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
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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 
onNewIntent(Intent intent, int serviceId)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 
stopSelfIfIdle(int startId)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 
isTransientFailure(int type)357     private static boolean isTransientFailure(int type) {
358         return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT;
359     }
360 
getTransactionType(int msgType)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 
launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork)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 
onNetworkUnavailable(int serviceId, int transactionType)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
onDestroy()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
onBind(Intent intent)425     public IBinder onBind(Intent intent) {
426         return null;
427     }
428 
429     /**
430      * Handle status change of Transaction (The Observable).
431      */
update(Observable observable)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 
createWakeLock()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 
acquireWakeLock()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 
releaseWakeLock()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 
beginMmsConnectivity()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 
endMmsConnectivity()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 {
ServiceHandler(Looper looper)586         public ServiceHandler(Looper looper) {
587             super(looper);
588         }
589 
decodeMessage(Message msg)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 
decodeTransactionType(int transactionType)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
handleMessage(Message msg)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 
markAllPendingTransactionsAsFailed()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 
processPendingTransaction(Transaction transaction, TransactionSettings settings)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          */
processTransaction(Transaction transaction)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 
renewMmsConnectivity()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
onReceive(Context context, Intent intent)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