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