• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007-2008 Esmertec AG.
3  * Copyright (C) 2007-2008 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.mms.transaction;
19 
20 import java.io.IOException;
21 import java.util.ArrayList;
22 
23 import android.app.Service;
24 import android.content.BroadcastReceiver;
25 import android.content.ContentUris;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.database.Cursor;
30 import android.net.ConnectivityManager;
31 import android.net.NetworkInfo;
32 import android.net.Uri;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.Message;
38 import android.os.PowerManager;
39 import android.provider.Telephony.Mms;
40 import android.provider.Telephony.MmsSms;
41 import android.provider.Telephony.MmsSms.PendingMessages;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.widget.Toast;
45 
46 import com.android.internal.telephony.Phone;
47 import com.android.internal.telephony.PhoneConstants;
48 import com.android.mms.LogTag;
49 import com.android.mms.R;
50 import com.android.mms.util.DownloadManager;
51 import com.android.mms.util.RateController;
52 import com.google.android.mms.pdu.GenericPdu;
53 import com.google.android.mms.pdu.NotificationInd;
54 import com.google.android.mms.pdu.PduHeaders;
55 import com.google.android.mms.pdu.PduParser;
56 import com.google.android.mms.pdu.PduPersister;
57 
58 /**
59  * The TransactionService of the MMS Client is responsible for handling requests
60  * to initiate client-transactions sent from:
61  * <ul>
62  * <li>The Proxy-Relay (Through Push messages)</li>
63  * <li>The composer/viewer activities of the MMS Client (Through intents)</li>
64  * </ul>
65  * The TransactionService runs locally in the same process as the application.
66  * It contains a HandlerThread to which messages are posted from the
67  * intent-receivers of this application.
68  * <p/>
69  * <b>IMPORTANT</b>: This is currently the only instance in the system in
70  * which simultaneous connectivity to both the mobile data network and
71  * a Wi-Fi network is allowed. This makes the code for handling network
72  * connectivity somewhat different than it is in other applications. In
73  * particular, we want to be able to send or receive MMS messages when
74  * a Wi-Fi connection is active (which implies that there is no connection
75  * to the mobile data network). This has two main consequences:
76  * <ul>
77  * <li>Testing for current network connectivity ({@link android.net.NetworkInfo#isConnected()} is
78  * not sufficient. Instead, the correct test is for network availability
79  * ({@link android.net.NetworkInfo#isAvailable()}).</li>
80  * <li>If the mobile data network is not in the connected state, but it is available,
81  * we must initiate setup of the mobile data connection, and defer handling
82  * the MMS transaction until the connection is established.</li>
83  * </ul>
84  */
85 public class TransactionService extends Service implements Observer {
86     private static final String TAG = "TransactionService";
87 
88     /**
89      * Used to identify notification intents broadcasted by the
90      * TransactionService when a Transaction is completed.
91      */
92     public static final String TRANSACTION_COMPLETED_ACTION =
93             "android.intent.action.TRANSACTION_COMPLETED_ACTION";
94 
95     /**
96      * Action for the Intent which is sent by Alarm service to launch
97      * TransactionService.
98      */
99     public static final String ACTION_ONALARM = "android.intent.action.ACTION_ONALARM";
100 
101     /**
102      * Action for the Intent which is sent when the user turns on the auto-retrieve setting.
103      * This service gets started to auto-retrieve any undownloaded messages.
104      */
105     public static final String ACTION_ENABLE_AUTO_RETRIEVE
106             = "android.intent.action.ACTION_ENABLE_AUTO_RETRIEVE";
107 
108     /**
109      * Used as extra key in notification intents broadcasted by the TransactionService
110      * when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents).
111      * Allowed values for this key are: TransactionState.INITIALIZED,
112      * TransactionState.SUCCESS, TransactionState.FAILED.
113      */
114     public static final String STATE = "state";
115 
116     /**
117      * Used as extra key in notification intents broadcasted by the TransactionService
118      * when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents).
119      * Allowed values for this key are any valid content uri.
120      */
121     public static final String STATE_URI = "uri";
122 
123     private static final int EVENT_TRANSACTION_REQUEST = 1;
124     private static final int EVENT_CONTINUE_MMS_CONNECTIVITY = 3;
125     private static final int EVENT_HANDLE_NEXT_PENDING_TRANSACTION = 4;
126     private static final int EVENT_NEW_INTENT = 5;
127     private static final int EVENT_QUIT = 100;
128 
129     private static final int TOAST_MSG_QUEUED = 1;
130     private static final int TOAST_DOWNLOAD_LATER = 2;
131     private static final int TOAST_NONE = -1;
132 
133     // How often to extend the use of the MMS APN while a transaction
134     // is still being processed.
135     private static final int APN_EXTENSION_WAIT = 30 * 1000;
136 
137     private ServiceHandler mServiceHandler;
138     private Looper mServiceLooper;
139     private final ArrayList<Transaction> mProcessing  = new ArrayList<Transaction>();
140     private final ArrayList<Transaction> mPending  = new ArrayList<Transaction>();
141     private ConnectivityManager mConnMgr;
142     private ConnectivityBroadcastReceiver mReceiver;
143 
144     private PowerManager.WakeLock mWakeLock;
145 
146     public Handler mToastHandler = new Handler() {
147         @Override
148         public void handleMessage(Message msg) {
149             String str = null;
150 
151             if (msg.what == TOAST_MSG_QUEUED) {
152                 str = getString(R.string.message_queued);
153             } else if (msg.what == TOAST_DOWNLOAD_LATER) {
154                 str = getString(R.string.download_later);
155             }
156 
157             if (str != null) {
158                 Toast.makeText(TransactionService.this, str,
159                         Toast.LENGTH_LONG).show();
160             }
161         }
162     };
163 
164     @Override
onCreate()165     public void onCreate() {
166         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
167             Log.v(TAG, "Creating TransactionService");
168         }
169 
170         // Start up the thread running the service.  Note that we create a
171         // separate thread because the service normally runs in the process's
172         // main thread, which we don't want to block.
173         HandlerThread thread = new HandlerThread("TransactionService");
174         thread.start();
175 
176         mServiceLooper = thread.getLooper();
177         mServiceHandler = new ServiceHandler(mServiceLooper);
178 
179         mReceiver = new ConnectivityBroadcastReceiver();
180         IntentFilter intentFilter = new IntentFilter();
181         intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
182         registerReceiver(mReceiver, intentFilter);
183     }
184 
185     @Override
onStartCommand(Intent intent, int flags, int startId)186     public int onStartCommand(Intent intent, int flags, int startId) {
187         if (intent != null) {
188             Message msg = mServiceHandler.obtainMessage(EVENT_NEW_INTENT);
189             msg.arg1 = startId;
190             msg.obj = intent;
191             mServiceHandler.sendMessage(msg);
192         }
193         return Service.START_NOT_STICKY;
194     }
195 
onNewIntent(Intent intent, int serviceId)196     public void onNewIntent(Intent intent, int serviceId) {
197         mConnMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
198         boolean noNetwork = !isNetworkAvailable();
199 
200         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
201             Log.v(TAG, "onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() +
202                     " intent=" + intent);
203             Log.v(TAG, "    networkAvailable=" + !noNetwork);
204         }
205 
206         String action = intent.getAction();
207         if (ACTION_ONALARM.equals(action) || ACTION_ENABLE_AUTO_RETRIEVE.equals(action) ||
208                 (intent.getExtras() == null)) {
209             // Scan database to find all pending operations.
210             Cursor cursor = PduPersister.getPduPersister(this).getPendingMessages(
211                     System.currentTimeMillis());
212             if (cursor != null) {
213                 try {
214                     int count = cursor.getCount();
215 
216                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
217                         Log.v(TAG, "onNewIntent: cursor.count=" + count + " action=" + action);
218                     }
219 
220                     if (count == 0) {
221                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
222                             Log.v(TAG, "onNewIntent: no pending messages. Stopping service.");
223                         }
224                         RetryScheduler.setRetryAlarm(this);
225                         stopSelfIfIdle(serviceId);
226                         return;
227                     }
228 
229                     int columnIndexOfMsgId = cursor.getColumnIndexOrThrow(PendingMessages.MSG_ID);
230                     int columnIndexOfMsgType = cursor.getColumnIndexOrThrow(
231                             PendingMessages.MSG_TYPE);
232 
233                     while (cursor.moveToNext()) {
234                         int msgType = cursor.getInt(columnIndexOfMsgType);
235                         int transactionType = getTransactionType(msgType);
236                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
237                             Log.v(TAG, "onNewIntent: msgType=" + msgType + " transactionType=" +
238                                     transactionType);
239                         }
240                         if (noNetwork) {
241                             onNetworkUnavailable(serviceId, transactionType);
242                             return;
243                         }
244                         switch (transactionType) {
245                             case -1:
246                                 break;
247                             case Transaction.RETRIEVE_TRANSACTION:
248                                 // If it's a transiently failed transaction,
249                                 // we should retry it in spite of current
250                                 // downloading mode. If the user just turned on the auto-retrieve
251                                 // option, we also retry those messages that don't have any errors.
252                                 int failureType = cursor.getInt(
253                                         cursor.getColumnIndexOrThrow(
254                                                 PendingMessages.ERROR_TYPE));
255                                 DownloadManager downloadManager = DownloadManager.getInstance();
256                                 boolean autoDownload = downloadManager.isAuto();
257                                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
258                                     Log.v(TAG, "onNewIntent: failureType=" + failureType +
259                                             " action=" + action + " isTransientFailure:" +
260                                             isTransientFailure(failureType) + " autoDownload=" +
261                                             autoDownload);
262                                 }
263                                 if (!autoDownload) {
264                                     // If autodownload is turned off, don't process the
265                                     // transaction.
266                                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
267                                         Log.v(TAG, "onNewIntent: skipping - autodownload off");
268                                     }
269                                     break;
270                                 }
271                                 // Logic is twisty. If there's no failure or the failure
272                                 // is a non-permanent failure, we want to process the transaction.
273                                 // Otherwise, break out and skip processing this transaction.
274                                 if (!(failureType == MmsSms.NO_ERROR ||
275                                         isTransientFailure(failureType))) {
276                                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
277                                         Log.v(TAG, "onNewIntent: skipping - permanent error");
278                                     }
279                                     break;
280                                 }
281                                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
282                                     Log.v(TAG, "onNewIntent: falling through and processing");
283                                 }
284                                // fall-through
285                             default:
286                                 Uri uri = ContentUris.withAppendedId(
287                                         Mms.CONTENT_URI,
288                                         cursor.getLong(columnIndexOfMsgId));
289                                 TransactionBundle args = new TransactionBundle(
290                                         transactionType, uri.toString());
291                                 // FIXME: We use the same startId for all MMs.
292                                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
293                                     Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri);
294                                 }
295                                 launchTransaction(serviceId, args, false);
296                                 break;
297                         }
298                     }
299                 } finally {
300                     cursor.close();
301                 }
302             } else {
303                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
304                     Log.v(TAG, "onNewIntent: no pending messages. Stopping service.");
305                 }
306                 RetryScheduler.setRetryAlarm(this);
307                 stopSelfIfIdle(serviceId);
308             }
309         } else {
310             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
311                 Log.v(TAG, "onNewIntent: launch transaction...");
312             }
313             // For launching NotificationTransaction and test purpose.
314             TransactionBundle args = new TransactionBundle(intent.getExtras());
315             launchTransaction(serviceId, args, noNetwork);
316         }
317     }
318 
stopSelfIfIdle(int startId)319     private void stopSelfIfIdle(int startId) {
320         synchronized (mProcessing) {
321             if (mProcessing.isEmpty() && mPending.isEmpty()) {
322                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
323                     Log.v(TAG, "stopSelfIfIdle: STOP!");
324                 }
325 
326                 stopSelf(startId);
327             }
328         }
329     }
330 
isTransientFailure(int type)331     private static boolean isTransientFailure(int type) {
332         return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT;
333     }
334 
isNetworkAvailable()335     private boolean isNetworkAvailable() {
336         if (mConnMgr == null) {
337             return false;
338         } else {
339             NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
340             return (ni == null ? false : ni.isAvailable());
341         }
342     }
343 
getTransactionType(int msgType)344     private int getTransactionType(int msgType) {
345         switch (msgType) {
346             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
347                 return Transaction.RETRIEVE_TRANSACTION;
348             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
349                 return Transaction.READREC_TRANSACTION;
350             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
351                 return Transaction.SEND_TRANSACTION;
352             default:
353                 Log.w(TAG, "Unrecognized MESSAGE_TYPE: " + msgType);
354                 return -1;
355         }
356     }
357 
launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork)358     private void launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork) {
359         if (noNetwork) {
360             Log.w(TAG, "launchTransaction: no network error!");
361             onNetworkUnavailable(serviceId, txnBundle.getTransactionType());
362             return;
363         }
364         Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST);
365         msg.arg1 = serviceId;
366         msg.obj = txnBundle;
367 
368         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
369             Log.v(TAG, "launchTransaction: sending message " + msg);
370         }
371         mServiceHandler.sendMessage(msg);
372     }
373 
onNetworkUnavailable(int serviceId, int transactionType)374     private void onNetworkUnavailable(int serviceId, int transactionType) {
375         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
376             Log.v(TAG, "onNetworkUnavailable: sid=" + serviceId + ", type=" + transactionType);
377         }
378 
379         int toastType = TOAST_NONE;
380         if (transactionType == Transaction.RETRIEVE_TRANSACTION) {
381             toastType = TOAST_DOWNLOAD_LATER;
382         } else if (transactionType == Transaction.SEND_TRANSACTION) {
383             toastType = TOAST_MSG_QUEUED;
384         }
385         if (toastType != TOAST_NONE) {
386             mToastHandler.sendEmptyMessage(toastType);
387         }
388         stopSelf(serviceId);
389     }
390 
391     @Override
onDestroy()392     public void onDestroy() {
393         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
394             Log.v(TAG, "Destroying TransactionService");
395         }
396         if (!mPending.isEmpty()) {
397             Log.w(TAG, "TransactionService exiting with transaction still pending");
398         }
399 
400         releaseWakeLock();
401 
402         unregisterReceiver(mReceiver);
403 
404         mServiceHandler.sendEmptyMessage(EVENT_QUIT);
405     }
406 
407     @Override
onBind(Intent intent)408     public IBinder onBind(Intent intent) {
409         return null;
410     }
411 
412     /**
413      * Handle status change of Transaction (The Observable).
414      */
update(Observable observable)415     public void update(Observable observable) {
416         Transaction transaction = (Transaction) observable;
417         int serviceId = transaction.getServiceId();
418 
419         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
420             Log.v(TAG, "update transaction " + serviceId);
421         }
422 
423         try {
424             synchronized (mProcessing) {
425                 mProcessing.remove(transaction);
426                 if (mPending.size() > 0) {
427                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
428                         Log.v(TAG, "update: handle next pending transaction...");
429                     }
430                     Message msg = mServiceHandler.obtainMessage(
431                             EVENT_HANDLE_NEXT_PENDING_TRANSACTION,
432                             transaction.getConnectionSettings());
433                     mServiceHandler.sendMessage(msg);
434                 }
435                 else {
436                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
437                         Log.v(TAG, "update: endMmsConnectivity");
438                     }
439                     endMmsConnectivity();
440                 }
441             }
442 
443             Intent intent = new Intent(TRANSACTION_COMPLETED_ACTION);
444             TransactionState state = transaction.getState();
445             int result = state.getState();
446             intent.putExtra(STATE, result);
447 
448             switch (result) {
449                 case TransactionState.SUCCESS:
450                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
451                         Log.v(TAG, "Transaction complete: " + serviceId);
452                     }
453 
454                     intent.putExtra(STATE_URI, state.getContentUri());
455 
456                     // Notify user in the system-wide notification area.
457                     switch (transaction.getType()) {
458                         case Transaction.NOTIFICATION_TRANSACTION:
459                         case Transaction.RETRIEVE_TRANSACTION:
460                             // We're already in a non-UI thread called from
461                             // NotificationTransacation.run(), so ok to block here.
462                             long threadId = MessagingNotification.getThreadId(
463                                     this, state.getContentUri());
464                             MessagingNotification.blockingUpdateNewMessageIndicator(this,
465                                     threadId,
466                                     false);
467                             MessagingNotification.updateDownloadFailedNotification(this);
468                             break;
469                         case Transaction.SEND_TRANSACTION:
470                             RateController.getInstance().update();
471                             break;
472                     }
473                     break;
474                 case TransactionState.FAILED:
475                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
476                         Log.v(TAG, "Transaction failed: " + serviceId);
477                     }
478                     break;
479                 default:
480                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
481                         Log.v(TAG, "Transaction state unknown: " +
482                                 serviceId + " " + result);
483                     }
484                     break;
485             }
486 
487             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
488                 Log.v(TAG, "update: broadcast transaction result " + result);
489             }
490             // Broadcast the result of the transaction.
491             sendBroadcast(intent);
492         } finally {
493             transaction.detach(this);
494             stopSelf(serviceId);
495         }
496     }
497 
createWakeLock()498     private synchronized void createWakeLock() {
499         // Create a new wake lock if we haven't made one yet.
500         if (mWakeLock == null) {
501             PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
502             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity");
503             mWakeLock.setReferenceCounted(false);
504         }
505     }
506 
acquireWakeLock()507     private void acquireWakeLock() {
508         // It's okay to double-acquire this because we are not using it
509         // in reference-counted mode.
510         mWakeLock.acquire();
511     }
512 
releaseWakeLock()513     private void releaseWakeLock() {
514         // Don't release the wake lock if it hasn't been created and acquired.
515         if (mWakeLock != null && mWakeLock.isHeld()) {
516             mWakeLock.release();
517         }
518     }
519 
beginMmsConnectivity()520     protected int beginMmsConnectivity() throws IOException {
521         // Take a wake lock so we don't fall asleep before the message is downloaded.
522         createWakeLock();
523 
524         int result = mConnMgr.startUsingNetworkFeature(
525                 ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
526 
527         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
528             Log.v(TAG, "beginMmsConnectivity: result=" + result);
529         }
530 
531         switch (result) {
532             case PhoneConstants.APN_ALREADY_ACTIVE:
533             case PhoneConstants.APN_REQUEST_STARTED:
534                 acquireWakeLock();
535                 return result;
536         }
537 
538         throw new IOException("Cannot establish MMS connectivity");
539     }
540 
endMmsConnectivity()541     protected void endMmsConnectivity() {
542         try {
543             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
544                 Log.v(TAG, "endMmsConnectivity");
545             }
546 
547             // cancel timer for renewal of lease
548             mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY);
549             if (mConnMgr != null) {
550                 mConnMgr.stopUsingNetworkFeature(
551                         ConnectivityManager.TYPE_MOBILE,
552                         Phone.FEATURE_ENABLE_MMS);
553             }
554         } finally {
555             releaseWakeLock();
556         }
557     }
558 
559     private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper)560         public ServiceHandler(Looper looper) {
561             super(looper);
562         }
563 
decodeMessage(Message msg)564         private String decodeMessage(Message msg) {
565             if (msg.what == EVENT_QUIT) {
566                 return "EVENT_QUIT";
567             } else if (msg.what == EVENT_CONTINUE_MMS_CONNECTIVITY) {
568                 return "EVENT_CONTINUE_MMS_CONNECTIVITY";
569             } else if (msg.what == EVENT_TRANSACTION_REQUEST) {
570                 return "EVENT_TRANSACTION_REQUEST";
571             } else if (msg.what == EVENT_HANDLE_NEXT_PENDING_TRANSACTION) {
572                 return "EVENT_HANDLE_NEXT_PENDING_TRANSACTION";
573             } else if (msg.what == EVENT_NEW_INTENT) {
574                 return "EVENT_NEW_INTENT";
575             }
576             return "unknown message.what";
577         }
578 
decodeTransactionType(int transactionType)579         private String decodeTransactionType(int transactionType) {
580             if (transactionType == Transaction.NOTIFICATION_TRANSACTION) {
581                 return "NOTIFICATION_TRANSACTION";
582             } else if (transactionType == Transaction.RETRIEVE_TRANSACTION) {
583                 return "RETRIEVE_TRANSACTION";
584             } else if (transactionType == Transaction.SEND_TRANSACTION) {
585                 return "SEND_TRANSACTION";
586             } else if (transactionType == Transaction.READREC_TRANSACTION) {
587                 return "READREC_TRANSACTION";
588             }
589             return "invalid transaction type";
590         }
591 
592         /**
593          * Handle incoming transaction requests.
594          * The incoming requests are initiated by the MMSC Server or by the
595          * MMS Client itself.
596          */
597         @Override
handleMessage(Message msg)598         public void handleMessage(Message msg) {
599             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
600                 Log.v(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg));
601             }
602 
603             Transaction transaction = null;
604 
605             switch (msg.what) {
606                 case EVENT_NEW_INTENT:
607                     onNewIntent((Intent)msg.obj, msg.arg1);
608                     break;
609 
610                 case EVENT_QUIT:
611                     getLooper().quit();
612                     return;
613 
614                 case EVENT_CONTINUE_MMS_CONNECTIVITY:
615                     synchronized (mProcessing) {
616                         if (mProcessing.isEmpty()) {
617                             return;
618                         }
619                     }
620 
621                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
622                         Log.v(TAG, "handle EVENT_CONTINUE_MMS_CONNECTIVITY event...");
623                     }
624 
625                     try {
626                         int result = beginMmsConnectivity();
627                         if (result != PhoneConstants.APN_ALREADY_ACTIVE) {
628                             Log.v(TAG, "Extending MMS connectivity returned " + result +
629                                     " instead of APN_ALREADY_ACTIVE");
630                             // Just wait for connectivity startup without
631                             // any new request of APN switch.
632                             return;
633                         }
634                     } catch (IOException e) {
635                         Log.w(TAG, "Attempt to extend use of MMS connectivity failed");
636                         return;
637                     }
638 
639                     // Restart timer
640                     renewMmsConnectivity();
641                     return;
642 
643                 case EVENT_TRANSACTION_REQUEST:
644                     int serviceId = msg.arg1;
645                     try {
646                         TransactionBundle args = (TransactionBundle) msg.obj;
647                         TransactionSettings transactionSettings;
648 
649                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
650                             Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" +
651                                     args.getMmscUrl() + " proxy port: " + args.getProxyAddress());
652                         }
653 
654                         // Set the connection settings for this transaction.
655                         // If these have not been set in args, load the default settings.
656                         String mmsc = args.getMmscUrl();
657                         if (mmsc != null) {
658                             transactionSettings = new TransactionSettings(
659                                     mmsc, args.getProxyAddress(), args.getProxyPort());
660                         } else {
661                             transactionSettings = new TransactionSettings(
662                                                     TransactionService.this, null);
663                         }
664 
665                         int transactionType = args.getTransactionType();
666 
667                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
668                             Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" +
669                                     transactionType + " " + decodeTransactionType(transactionType));
670                         }
671 
672                         // Create appropriate transaction
673                         switch (transactionType) {
674                             case Transaction.NOTIFICATION_TRANSACTION:
675                                 String uri = args.getUri();
676                                 if (uri != null) {
677                                     transaction = new NotificationTransaction(
678                                             TransactionService.this, serviceId,
679                                             transactionSettings, uri);
680                                 } else {
681                                     // Now it's only used for test purpose.
682                                     byte[] pushData = args.getPushData();
683                                     PduParser parser = new PduParser(pushData);
684                                     GenericPdu ind = parser.parse();
685 
686                                     int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
687                                     if ((ind != null) && (ind.getMessageType() == type)) {
688                                         transaction = new NotificationTransaction(
689                                                 TransactionService.this, serviceId,
690                                                 transactionSettings, (NotificationInd) ind);
691                                     } else {
692                                         Log.e(TAG, "Invalid PUSH data.");
693                                         transaction = null;
694                                         return;
695                                     }
696                                 }
697                                 break;
698                             case Transaction.RETRIEVE_TRANSACTION:
699                                 transaction = new RetrieveTransaction(
700                                         TransactionService.this, serviceId,
701                                         transactionSettings, args.getUri());
702                                 break;
703                             case Transaction.SEND_TRANSACTION:
704                                 transaction = new SendTransaction(
705                                         TransactionService.this, serviceId,
706                                         transactionSettings, args.getUri());
707                                 break;
708                             case Transaction.READREC_TRANSACTION:
709                                 transaction = new ReadRecTransaction(
710                                         TransactionService.this, serviceId,
711                                         transactionSettings, args.getUri());
712                                 break;
713                             default:
714                                 Log.w(TAG, "Invalid transaction type: " + serviceId);
715                                 transaction = null;
716                                 return;
717                         }
718 
719                         if (!processTransaction(transaction)) {
720                             transaction = null;
721                             return;
722                         }
723 
724                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
725                             Log.v(TAG, "Started processing of incoming message: " + msg);
726                         }
727                     } catch (Exception ex) {
728                         Log.w(TAG, "Exception occurred while handling message: " + msg, ex);
729 
730                         if (transaction != null) {
731                             try {
732                                 transaction.detach(TransactionService.this);
733                                 if (mProcessing.contains(transaction)) {
734                                     synchronized (mProcessing) {
735                                         mProcessing.remove(transaction);
736                                     }
737                                 }
738                             } catch (Throwable t) {
739                                 Log.e(TAG, "Unexpected Throwable.", t);
740                             } finally {
741                                 // Set transaction to null to allow stopping the
742                                 // transaction service.
743                                 transaction = null;
744                             }
745                         }
746                     } finally {
747                         if (transaction == null) {
748                             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
749                                 Log.v(TAG, "Transaction was null. Stopping self: " + serviceId);
750                             }
751                             endMmsConnectivity();
752                             stopSelf(serviceId);
753                         }
754                     }
755                     return;
756                 case EVENT_HANDLE_NEXT_PENDING_TRANSACTION:
757                     processPendingTransaction(transaction, (TransactionSettings) msg.obj);
758                     return;
759                 default:
760                     Log.w(TAG, "what=" + msg.what);
761                     return;
762             }
763         }
764 
processPendingTransaction(Transaction transaction, TransactionSettings settings)765         public void processPendingTransaction(Transaction transaction,
766                                                TransactionSettings settings) {
767 
768             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
769                 Log.v(TAG, "processPendingTxn: transaction=" + transaction);
770             }
771 
772             int numProcessTransaction = 0;
773             synchronized (mProcessing) {
774                 if (mPending.size() != 0) {
775                     transaction = mPending.remove(0);
776                 }
777                 numProcessTransaction = mProcessing.size();
778             }
779 
780             if (transaction != null) {
781                 if (settings != null) {
782                     transaction.setConnectionSettings(settings);
783                 }
784 
785                 /*
786                  * Process deferred transaction
787                  */
788                 try {
789                     int serviceId = transaction.getServiceId();
790 
791                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
792                         Log.v(TAG, "processPendingTxn: process " + serviceId);
793                     }
794 
795                     if (processTransaction(transaction)) {
796                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
797                             Log.v(TAG, "Started deferred processing of transaction  "
798                                     + transaction);
799                         }
800                     } else {
801                         transaction = null;
802                         stopSelf(serviceId);
803                     }
804                 } catch (IOException e) {
805                     Log.w(TAG, e.getMessage(), e);
806                 }
807             } else {
808                 if (numProcessTransaction == 0) {
809                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
810                         Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity");
811                     }
812                     endMmsConnectivity();
813                 }
814             }
815         }
816 
817         /**
818          * Internal method to begin processing a transaction.
819          * @param transaction the transaction. Must not be {@code null}.
820          * @return {@code true} if process has begun or will begin. {@code false}
821          * if the transaction should be discarded.
822          * @throws IOException if connectivity for MMS traffic could not be
823          * established.
824          */
processTransaction(Transaction transaction)825         private boolean processTransaction(Transaction transaction) throws IOException {
826             // Check if transaction already processing
827             synchronized (mProcessing) {
828                 for (Transaction t : mPending) {
829                     if (t.isEquivalent(transaction)) {
830                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
831                             Log.v(TAG, "Transaction already pending: " +
832                                     transaction.getServiceId());
833                         }
834                         return true;
835                     }
836                 }
837                 for (Transaction t : mProcessing) {
838                     if (t.isEquivalent(transaction)) {
839                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
840                             Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId());
841                         }
842                         return true;
843                     }
844                 }
845 
846                 /*
847                 * Make sure that the network connectivity necessary
848                 * for MMS traffic is enabled. If it is not, we need
849                 * to defer processing the transaction until
850                 * connectivity is established.
851                 */
852                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
853                     Log.v(TAG, "processTransaction: call beginMmsConnectivity...");
854                 }
855                 int connectivityResult = beginMmsConnectivity();
856                 if (connectivityResult == PhoneConstants.APN_REQUEST_STARTED) {
857                     mPending.add(transaction);
858                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
859                         Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " +
860                                 "defer transaction pending MMS connectivity");
861                     }
862                     return true;
863                 }
864 
865                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
866                     Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction);
867                 }
868                 mProcessing.add(transaction);
869             }
870 
871             // Set a timer to keep renewing our "lease" on the MMS connection
872             sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
873                                APN_EXTENSION_WAIT);
874 
875             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
876                 Log.v(TAG, "processTransaction: starting transaction " + transaction);
877             }
878 
879             // Attach to transaction and process it
880             transaction.attach(TransactionService.this);
881             transaction.process();
882             return true;
883         }
884     }
885 
renewMmsConnectivity()886     private void renewMmsConnectivity() {
887         // Set a timer to keep renewing our "lease" on the MMS connection
888         mServiceHandler.sendMessageDelayed(
889                 mServiceHandler.obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
890                            APN_EXTENSION_WAIT);
891     }
892 
893     private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
894         @Override
onReceive(Context context, Intent intent)895         public void onReceive(Context context, Intent intent) {
896             String action = intent.getAction();
897             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
898                 Log.w(TAG, "ConnectivityBroadcastReceiver.onReceive() action: " + action);
899             }
900 
901             if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
902                 return;
903             }
904 
905             NetworkInfo mmsNetworkInfo = null;
906 
907             if (mConnMgr != null) {
908                 mmsNetworkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
909             } else {
910                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
911                     Log.v(TAG, "mConnMgr is null, bail");
912                 }
913             }
914 
915             /*
916              * If we are being informed that connectivity has been established
917              * to allow MMS traffic, then proceed with processing the pending
918              * transaction, if any.
919              */
920 
921             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
922                 Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + mmsNetworkInfo);
923             }
924 
925             // Check availability of the mobile network.
926             if ((mmsNetworkInfo == null)) {
927                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
928                     Log.v(TAG, "mms type is null, bail");
929                 }
930             } else {
931                 // This is a very specific fix to handle the case where the phone receives an
932                 // incoming call during the time we're trying to setup the mms connection.
933                 // When the call ends, restart the process of mms connectivity.
934                 if (Phone.REASON_VOICE_CALL_ENDED.equals(mmsNetworkInfo.getReason())) {
935                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
936                         Log.v(TAG, "   reason is " + Phone.REASON_VOICE_CALL_ENDED +
937                                 ", retrying mms connectivity");
938                     }
939                     renewMmsConnectivity();
940                     return;
941                 }
942 
943                 if (mmsNetworkInfo.isConnected()) {
944                     TransactionSettings settings = new TransactionSettings(
945                             TransactionService.this, mmsNetworkInfo.getExtraInfo());
946                     // If this APN doesn't have an MMSC, wait for one that does.
947                     if (TextUtils.isEmpty(settings.getMmscUrl())) {
948                         Log.v(TAG, "   empty MMSC url, bail");
949                         return;
950                     }
951                     mServiceHandler.processPendingTransaction(null, settings);
952                 } else {
953                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
954                         Log.v(TAG, "   TYPE_MOBILE_MMS not connected, bail");
955                     }
956 
957                     // Retry mms connectivity once it's possible to connect
958                     if (mmsNetworkInfo.isAvailable()) {
959                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
960                             Log.v(TAG, "   retrying mms connectivity for it's available");
961                         }
962                         renewMmsConnectivity();
963                     }
964                 }
965             }
966         }
967     };
968 }
969