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