Home | History | Annotate | Download | only in transaction
      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
    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
    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 
    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 
    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 
    331     private static boolean isTransientFailure(int type) {
    332         return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT;
    333     }
    334 
    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 
    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 
    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 
    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
    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
    408     public IBinder onBind(Intent intent) {
    409         return null;
    410     }
    411 
    412     /**
    413      * Handle status change of Transaction (The Observable).
    414      */
    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 
    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 
    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 
    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 
    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 
    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 {
    560         public ServiceHandler(Looper looper) {
    561             super(looper);
    562         }
    563 
    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 
    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
    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 
    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          */
    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 
    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
    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