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