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