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