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