• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = LogTag.TAG;
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
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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 
onNewIntent(Intent intent, int serviceId)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                                     // Re-enable "download" button if auto-download is off
284                                     Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI,
285                                             cursor.getLong(columnIndexOfMsgId));
286                                     downloadManager.markState(uri,
287                                             DownloadManager.STATE_SKIP_RETRYING);
288                                     break;
289                                 }
290                                 // Logic is twisty. If there's no failure or the failure
291                                 // is a non-permanent failure, we want to process the transaction.
292                                 // Otherwise, break out and skip processing this transaction.
293                                 if (!(failureType == MmsSms.NO_ERROR ||
294                                         isTransientFailure(failureType))) {
295                                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
296                                         Log.v(TAG, "onNewIntent: skipping - permanent error");
297                                     }
298                                     break;
299                                 }
300                                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
301                                     Log.v(TAG, "onNewIntent: falling through and processing");
302                                 }
303                                // fall-through
304                             default:
305                                 Uri uri = ContentUris.withAppendedId(
306                                         Mms.CONTENT_URI,
307                                         cursor.getLong(columnIndexOfMsgId));
308                                 TransactionBundle args = new TransactionBundle(
309                                         transactionType, uri.toString());
310                                 // FIXME: We use the same startId for all MMs.
311                                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
312                                     Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri);
313                                 }
314                                 launchTransaction(serviceId, args, false);
315                                 break;
316                         }
317                     }
318                 } finally {
319                     cursor.close();
320                 }
321             } else {
322                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
323                     Log.v(TAG, "onNewIntent: no pending messages. Stopping service.");
324                 }
325                 RetryScheduler.setRetryAlarm(this);
326                 stopSelfIfIdle(serviceId);
327             }
328         } else {
329             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
330                 Log.v(TAG, "onNewIntent: launch transaction...");
331             }
332             // For launching NotificationTransaction and test purpose.
333             TransactionBundle args = new TransactionBundle(intent.getExtras());
334             launchTransaction(serviceId, args, noNetwork);
335         }
336     }
337 
stopSelfIfIdle(int startId)338     private void stopSelfIfIdle(int startId) {
339         synchronized (mProcessing) {
340             if (mProcessing.isEmpty() && mPending.isEmpty()) {
341                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
342                     Log.v(TAG, "stopSelfIfIdle: STOP!");
343                 }
344 
345                 stopSelf(startId);
346             }
347         }
348     }
349 
isTransientFailure(int type)350     private static boolean isTransientFailure(int type) {
351         return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT;
352     }
353 
getTransactionType(int msgType)354     private int getTransactionType(int msgType) {
355         switch (msgType) {
356             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
357                 return Transaction.RETRIEVE_TRANSACTION;
358             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
359                 return Transaction.READREC_TRANSACTION;
360             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
361                 return Transaction.SEND_TRANSACTION;
362             default:
363                 Log.w(TAG, "Unrecognized MESSAGE_TYPE: " + msgType);
364                 return -1;
365         }
366     }
367 
launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork)368     private void launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork) {
369         if (noNetwork) {
370             Log.w(TAG, "launchTransaction: no network error!");
371             onNetworkUnavailable(serviceId, txnBundle.getTransactionType());
372             return;
373         }
374         Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST);
375         msg.arg1 = serviceId;
376         msg.obj = txnBundle;
377 
378         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
379             Log.v(TAG, "launchTransaction: sending message " + msg);
380         }
381         mServiceHandler.sendMessage(msg);
382     }
383 
onNetworkUnavailable(int serviceId, int transactionType)384     private void onNetworkUnavailable(int serviceId, int transactionType) {
385         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
386             Log.v(TAG, "onNetworkUnavailable: sid=" + serviceId + ", type=" + transactionType);
387         }
388 
389         int toastType = TOAST_NONE;
390         if (transactionType == Transaction.RETRIEVE_TRANSACTION) {
391             toastType = TOAST_DOWNLOAD_LATER;
392         } else if (transactionType == Transaction.SEND_TRANSACTION) {
393             toastType = TOAST_MSG_QUEUED;
394         }
395         if (toastType != TOAST_NONE) {
396             mToastHandler.sendEmptyMessage(toastType);
397         }
398         stopSelf(serviceId);
399     }
400 
401     @Override
onDestroy()402     public void onDestroy() {
403         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
404             Log.v(TAG, "Destroying TransactionService");
405         }
406         if (!mPending.isEmpty()) {
407             Log.w(TAG, "TransactionService exiting with transaction still pending");
408         }
409 
410         releaseWakeLock();
411 
412         unregisterReceiver(mReceiver);
413 
414         mServiceHandler.sendEmptyMessage(EVENT_QUIT);
415     }
416 
417     @Override
onBind(Intent intent)418     public IBinder onBind(Intent intent) {
419         return null;
420     }
421 
422     /**
423      * Handle status change of Transaction (The Observable).
424      */
update(Observable observable)425     public void update(Observable observable) {
426         Transaction transaction = (Transaction) observable;
427         int serviceId = transaction.getServiceId();
428 
429         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
430             Log.v(TAG, "update transaction " + serviceId);
431         }
432 
433         try {
434             synchronized (mProcessing) {
435                 mProcessing.remove(transaction);
436                 if (mPending.size() > 0) {
437                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
438                         Log.v(TAG, "update: handle next pending transaction...");
439                     }
440                     Message msg = mServiceHandler.obtainMessage(
441                             EVENT_HANDLE_NEXT_PENDING_TRANSACTION,
442                             transaction.getConnectionSettings());
443                     mServiceHandler.sendMessage(msg);
444                 }
445                 else if (mProcessing.isEmpty()) {
446                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
447                         Log.v(TAG, "update: endMmsConnectivity");
448                     }
449                     endMmsConnectivity();
450                 } else {
451                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
452                         Log.v(TAG, "update: mProcessing is not empty");
453                     }
454                 }
455             }
456 
457             Intent intent = new Intent(TRANSACTION_COMPLETED_ACTION);
458             TransactionState state = transaction.getState();
459             int result = state.getState();
460             intent.putExtra(STATE, result);
461 
462             switch (result) {
463                 case TransactionState.SUCCESS:
464                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
465                         Log.v(TAG, "Transaction complete: " + serviceId);
466                     }
467 
468                     intent.putExtra(STATE_URI, state.getContentUri());
469 
470                     // Notify user in the system-wide notification area.
471                     switch (transaction.getType()) {
472                         case Transaction.NOTIFICATION_TRANSACTION:
473                         case Transaction.RETRIEVE_TRANSACTION:
474                             // We're already in a non-UI thread called from
475                             // NotificationTransacation.run(), so ok to block here.
476                             long threadId = MessagingNotification.getThreadId(
477                                     this, state.getContentUri());
478                             MessagingNotification.blockingUpdateNewMessageIndicator(this,
479                                     threadId,
480                                     false);
481                             MessagingNotification.updateDownloadFailedNotification(this);
482                             break;
483                         case Transaction.SEND_TRANSACTION:
484                             RateController.getInstance().update();
485                             break;
486                     }
487                     break;
488                 case TransactionState.FAILED:
489                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
490                         Log.v(TAG, "Transaction failed: " + serviceId);
491                     }
492                     break;
493                 default:
494                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
495                         Log.v(TAG, "Transaction state unknown: " +
496                                 serviceId + " " + result);
497                     }
498                     break;
499             }
500 
501             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
502                 Log.v(TAG, "update: broadcast transaction result " + result);
503             }
504             // Broadcast the result of the transaction.
505             sendBroadcast(intent);
506         } finally {
507             transaction.detach(this);
508             stopSelfIfIdle(serviceId);
509         }
510     }
511 
createWakeLock()512     private synchronized void createWakeLock() {
513         // Create a new wake lock if we haven't made one yet.
514         if (mWakeLock == null) {
515             PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
516             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity");
517             mWakeLock.setReferenceCounted(false);
518         }
519     }
520 
acquireWakeLock()521     private void acquireWakeLock() {
522         // It's okay to double-acquire this because we are not using it
523         // in reference-counted mode.
524         Log.v(TAG, "mms acquireWakeLock");
525         mWakeLock.acquire();
526     }
527 
releaseWakeLock()528     private void releaseWakeLock() {
529         // Don't release the wake lock if it hasn't been created and acquired.
530         if (mWakeLock != null && mWakeLock.isHeld()) {
531             Log.v(TAG, "mms releaseWakeLock");
532             mWakeLock.release();
533         }
534     }
535 
beginMmsConnectivity()536     protected int beginMmsConnectivity() throws IOException {
537         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
538             Log.v(TAG, "beginMmsConnectivity");
539         }
540         // Take a wake lock so we don't fall asleep before the message is downloaded.
541         createWakeLock();
542 
543         int result = mConnMgr.startUsingNetworkFeature(
544                 ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
545 
546         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
547             Log.v(TAG, "beginMmsConnectivity: result=" + result);
548         }
549 
550         switch (result) {
551             case PhoneConstants.APN_ALREADY_ACTIVE:
552             case PhoneConstants.APN_REQUEST_STARTED:
553                 acquireWakeLock();
554                 return result;
555         }
556 
557         throw new IOException("Cannot establish MMS connectivity");
558     }
559 
endMmsConnectivity()560     protected void endMmsConnectivity() {
561         try {
562             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
563                 Log.v(TAG, "endMmsConnectivity");
564             }
565 
566             // cancel timer for renewal of lease
567             mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY);
568             if (mConnMgr != null) {
569                 mConnMgr.stopUsingNetworkFeature(
570                         ConnectivityManager.TYPE_MOBILE,
571                         Phone.FEATURE_ENABLE_MMS);
572             }
573         } finally {
574             releaseWakeLock();
575         }
576     }
577 
578     private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper)579         public ServiceHandler(Looper looper) {
580             super(looper);
581         }
582 
decodeMessage(Message msg)583         private String decodeMessage(Message msg) {
584             if (msg.what == EVENT_QUIT) {
585                 return "EVENT_QUIT";
586             } else if (msg.what == EVENT_CONTINUE_MMS_CONNECTIVITY) {
587                 return "EVENT_CONTINUE_MMS_CONNECTIVITY";
588             } else if (msg.what == EVENT_TRANSACTION_REQUEST) {
589                 return "EVENT_TRANSACTION_REQUEST";
590             } else if (msg.what == EVENT_HANDLE_NEXT_PENDING_TRANSACTION) {
591                 return "EVENT_HANDLE_NEXT_PENDING_TRANSACTION";
592             } else if (msg.what == EVENT_NEW_INTENT) {
593                 return "EVENT_NEW_INTENT";
594             }
595             return "unknown message.what";
596         }
597 
decodeTransactionType(int transactionType)598         private String decodeTransactionType(int transactionType) {
599             if (transactionType == Transaction.NOTIFICATION_TRANSACTION) {
600                 return "NOTIFICATION_TRANSACTION";
601             } else if (transactionType == Transaction.RETRIEVE_TRANSACTION) {
602                 return "RETRIEVE_TRANSACTION";
603             } else if (transactionType == Transaction.SEND_TRANSACTION) {
604                 return "SEND_TRANSACTION";
605             } else if (transactionType == Transaction.READREC_TRANSACTION) {
606                 return "READREC_TRANSACTION";
607             }
608             return "invalid transaction type";
609         }
610 
611         /**
612          * Handle incoming transaction requests.
613          * The incoming requests are initiated by the MMSC Server or by the
614          * MMS Client itself.
615          */
616         @Override
handleMessage(Message msg)617         public void handleMessage(Message msg) {
618             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
619                 Log.v(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg));
620             }
621 
622             Transaction transaction = null;
623 
624             switch (msg.what) {
625                 case EVENT_NEW_INTENT:
626                     onNewIntent((Intent)msg.obj, msg.arg1);
627                     break;
628 
629                 case EVENT_QUIT:
630                     getLooper().quit();
631                     return;
632 
633                 case EVENT_CONTINUE_MMS_CONNECTIVITY:
634                     synchronized (mProcessing) {
635                         if (mProcessing.isEmpty()) {
636                             return;
637                         }
638                     }
639 
640                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
641                         Log.v(TAG, "handle EVENT_CONTINUE_MMS_CONNECTIVITY event...");
642                     }
643 
644                     try {
645                         int result = beginMmsConnectivity();
646                         if (result != PhoneConstants.APN_ALREADY_ACTIVE) {
647                             Log.v(TAG, "Extending MMS connectivity returned " + result +
648                                     " instead of APN_ALREADY_ACTIVE");
649                             // Just wait for connectivity startup without
650                             // any new request of APN switch.
651                             return;
652                         }
653                     } catch (IOException e) {
654                         Log.w(TAG, "Attempt to extend use of MMS connectivity failed");
655                         return;
656                     }
657 
658                     // Restart timer
659                     renewMmsConnectivity();
660                     return;
661 
662                 case EVENT_TRANSACTION_REQUEST:
663                     int serviceId = msg.arg1;
664                     try {
665                         TransactionBundle args = (TransactionBundle) msg.obj;
666                         TransactionSettings transactionSettings;
667 
668                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
669                             Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" +
670                                     args.getMmscUrl() + " proxy port: " + args.getProxyAddress());
671                         }
672 
673                         // Set the connection settings for this transaction.
674                         // If these have not been set in args, load the default settings.
675                         String mmsc = args.getMmscUrl();
676                         if (mmsc != null) {
677                             transactionSettings = new TransactionSettings(
678                                     mmsc, args.getProxyAddress(), args.getProxyPort());
679                         } else {
680                             transactionSettings = new TransactionSettings(
681                                                     TransactionService.this, null);
682                         }
683 
684                         int transactionType = args.getTransactionType();
685 
686                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
687                             Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" +
688                                     transactionType + " " + decodeTransactionType(transactionType));
689                         }
690 
691                         // Create appropriate transaction
692                         switch (transactionType) {
693                             case Transaction.NOTIFICATION_TRANSACTION:
694                                 String uri = args.getUri();
695                                 if (uri != null) {
696                                     transaction = new NotificationTransaction(
697                                             TransactionService.this, serviceId,
698                                             transactionSettings, uri);
699                                 } else {
700                                     // Now it's only used for test purpose.
701                                     byte[] pushData = args.getPushData();
702                                     PduParser parser = new PduParser(pushData);
703                                     GenericPdu ind = parser.parse();
704 
705                                     int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
706                                     if ((ind != null) && (ind.getMessageType() == type)) {
707                                         transaction = new NotificationTransaction(
708                                                 TransactionService.this, serviceId,
709                                                 transactionSettings, (NotificationInd) ind);
710                                     } else {
711                                         Log.e(TAG, "Invalid PUSH data.");
712                                         transaction = null;
713                                         return;
714                                     }
715                                 }
716                                 break;
717                             case Transaction.RETRIEVE_TRANSACTION:
718                                 transaction = new RetrieveTransaction(
719                                         TransactionService.this, serviceId,
720                                         transactionSettings, args.getUri());
721                                 break;
722                             case Transaction.SEND_TRANSACTION:
723                                 transaction = new SendTransaction(
724                                         TransactionService.this, serviceId,
725                                         transactionSettings, args.getUri());
726                                 break;
727                             case Transaction.READREC_TRANSACTION:
728                                 transaction = new ReadRecTransaction(
729                                         TransactionService.this, serviceId,
730                                         transactionSettings, args.getUri());
731                                 break;
732                             default:
733                                 Log.w(TAG, "Invalid transaction type: " + serviceId);
734                                 transaction = null;
735                                 return;
736                         }
737 
738                         if (!processTransaction(transaction)) {
739                             transaction = null;
740                             return;
741                         }
742 
743                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
744                             Log.v(TAG, "Started processing of incoming message: " + msg);
745                         }
746                     } catch (Exception ex) {
747                         Log.w(TAG, "Exception occurred while handling message: " + msg, ex);
748 
749                         if (transaction != null) {
750                             try {
751                                 transaction.detach(TransactionService.this);
752                                 if (mProcessing.contains(transaction)) {
753                                     synchronized (mProcessing) {
754                                         mProcessing.remove(transaction);
755                                     }
756                                 }
757                             } catch (Throwable t) {
758                                 Log.e(TAG, "Unexpected Throwable.", t);
759                             } finally {
760                                 // Set transaction to null to allow stopping the
761                                 // transaction service.
762                                 transaction = null;
763                             }
764                         }
765                     } finally {
766                         if (transaction == null) {
767                             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
768                                 Log.v(TAG, "Transaction was null. Stopping self: " + serviceId);
769                             }
770                             endMmsConnectivity();
771                             stopSelf(serviceId);
772                         }
773                     }
774                     return;
775                 case EVENT_HANDLE_NEXT_PENDING_TRANSACTION:
776                     processPendingTransaction(transaction, (TransactionSettings) msg.obj);
777                     return;
778                 default:
779                     Log.w(TAG, "what=" + msg.what);
780                     return;
781             }
782         }
783 
markAllPendingTransactionsAsFailed()784         public void markAllPendingTransactionsAsFailed() {
785             synchronized (mProcessing) {
786                 while (mPending.size() != 0) {
787                     Transaction transaction = mPending.remove(0);
788                     transaction.mTransactionState.setState(TransactionState.FAILED);
789                     if (transaction instanceof SendTransaction) {
790                         Uri uri = ((SendTransaction)transaction).mSendReqURI;
791                         transaction.mTransactionState.setContentUri(uri);
792                         int respStatus = PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM;
793                         ContentValues values = new ContentValues(1);
794                         values.put(Mms.RESPONSE_STATUS, respStatus);
795 
796                         SqliteWrapper.update(TransactionService.this,
797                                 TransactionService.this.getContentResolver(),
798                                 uri, values, null, null);
799                     }
800                     transaction.notifyObservers();
801                 }
802             }
803         }
804 
processPendingTransaction(Transaction transaction, TransactionSettings settings)805         public void processPendingTransaction(Transaction transaction,
806                                                TransactionSettings settings) {
807 
808             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
809                 Log.v(TAG, "processPendingTxn: transaction=" + transaction);
810             }
811 
812             int numProcessTransaction = 0;
813             synchronized (mProcessing) {
814                 if (mPending.size() != 0) {
815                     transaction = mPending.remove(0);
816                 }
817                 numProcessTransaction = mProcessing.size();
818             }
819 
820             if (transaction != null) {
821                 if (settings != null) {
822                     transaction.setConnectionSettings(settings);
823                 }
824 
825                 /*
826                  * Process deferred transaction
827                  */
828                 try {
829                     int serviceId = transaction.getServiceId();
830 
831                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
832                         Log.v(TAG, "processPendingTxn: process " + serviceId);
833                     }
834 
835                     if (processTransaction(transaction)) {
836                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
837                             Log.v(TAG, "Started deferred processing of transaction  "
838                                     + transaction);
839                         }
840                     } else {
841                         transaction = null;
842                         stopSelf(serviceId);
843                     }
844                 } catch (IOException e) {
845                     Log.w(TAG, e.getMessage(), e);
846                 }
847             } else {
848                 if (numProcessTransaction == 0) {
849                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
850                         Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity");
851                     }
852                     endMmsConnectivity();
853                 }
854             }
855         }
856 
857         /**
858          * Internal method to begin processing a transaction.
859          * @param transaction the transaction. Must not be {@code null}.
860          * @return {@code true} if process has begun or will begin. {@code false}
861          * if the transaction should be discarded.
862          * @throws IOException if connectivity for MMS traffic could not be
863          * established.
864          */
processTransaction(Transaction transaction)865         private boolean processTransaction(Transaction transaction) throws IOException {
866             // Check if transaction already processing
867             synchronized (mProcessing) {
868                 for (Transaction t : mPending) {
869                     if (t.isEquivalent(transaction)) {
870                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
871                             Log.v(TAG, "Transaction already pending: " +
872                                     transaction.getServiceId());
873                         }
874                         return true;
875                     }
876                 }
877                 for (Transaction t : mProcessing) {
878                     if (t.isEquivalent(transaction)) {
879                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
880                             Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId());
881                         }
882                         return true;
883                     }
884                 }
885 
886                 /*
887                 * Make sure that the network connectivity necessary
888                 * for MMS traffic is enabled. If it is not, we need
889                 * to defer processing the transaction until
890                 * connectivity is established.
891                 */
892                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
893                     Log.v(TAG, "processTransaction: call beginMmsConnectivity...");
894                 }
895                 int connectivityResult = beginMmsConnectivity();
896                 if (connectivityResult == PhoneConstants.APN_REQUEST_STARTED) {
897                     mPending.add(transaction);
898                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
899                         Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " +
900                                 "defer transaction pending MMS connectivity");
901                     }
902                     return true;
903                 }
904                 // If there is already a transaction in processing list, because of the previous
905                 // beginMmsConnectivity call and there is another transaction just at a time,
906                 // when the pdp is connected, there will be a case of adding the new transaction
907                 // to the Processing list. But Processing list is never traversed to
908                 // resend, resulting in transaction not completed/sent.
909                 if (mProcessing.size() > 0) {
910                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
911                         Log.v(TAG, "Adding transaction to 'mPending' list: " + transaction);
912                     }
913                     mPending.add(transaction);
914                     return true;
915                 } else {
916                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
917                         Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction);
918                     }
919                     mProcessing.add(transaction);
920                }
921             }
922 
923             // Set a timer to keep renewing our "lease" on the MMS connection
924             sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
925                                APN_EXTENSION_WAIT);
926 
927             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
928                 Log.v(TAG, "processTransaction: starting transaction " + transaction);
929             }
930 
931             // Attach to transaction and process it
932             transaction.attach(TransactionService.this);
933             transaction.process();
934             return true;
935         }
936     }
937 
renewMmsConnectivity()938     private void renewMmsConnectivity() {
939         // Set a timer to keep renewing our "lease" on the MMS connection
940         mServiceHandler.sendMessageDelayed(
941                 mServiceHandler.obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
942                            APN_EXTENSION_WAIT);
943     }
944 
945     private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
946         @Override
onReceive(Context context, Intent intent)947         public void onReceive(Context context, Intent intent) {
948             String action = intent.getAction();
949             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
950                 Log.w(TAG, "ConnectivityBroadcastReceiver.onReceive() action: " + action);
951             }
952 
953             if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
954                 return;
955             }
956 
957             NetworkInfo mmsNetworkInfo = null;
958 
959             if (mConnMgr != null && mConnMgr.getMobileDataEnabled()) {
960                 mmsNetworkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
961             } else {
962                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
963                     Log.v(TAG, "mConnMgr is null, bail");
964                 }
965             }
966 
967             /*
968              * If we are being informed that connectivity has been established
969              * to allow MMS traffic, then proceed with processing the pending
970              * transaction, if any.
971              */
972 
973             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
974                 Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + mmsNetworkInfo);
975             }
976 
977             // Check availability of the mobile network.
978             if (mmsNetworkInfo == null) {
979                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
980                     Log.v(TAG, "mms type is null or mobile data is turned off, bail");
981                 }
982             } else {
983                 // This is a very specific fix to handle the case where the phone receives an
984                 // incoming call during the time we're trying to setup the mms connection.
985                 // When the call ends, restart the process of mms connectivity.
986                 if (Phone.REASON_VOICE_CALL_ENDED.equals(mmsNetworkInfo.getReason())) {
987                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
988                         Log.v(TAG, "   reason is " + Phone.REASON_VOICE_CALL_ENDED +
989                                 ", retrying mms connectivity");
990                     }
991                     renewMmsConnectivity();
992                     return;
993                 }
994 
995                 if (mmsNetworkInfo.isConnected()) {
996                     TransactionSettings settings = new TransactionSettings(
997                             TransactionService.this, mmsNetworkInfo.getExtraInfo());
998                     // If this APN doesn't have an MMSC, mark everything as failed and bail.
999                     if (TextUtils.isEmpty(settings.getMmscUrl())) {
1000                         Log.v(TAG, "   empty MMSC url, bail");
1001                         mToastHandler.sendEmptyMessage(TOAST_NO_APN);
1002                         mServiceHandler.markAllPendingTransactionsAsFailed();
1003                         endMmsConnectivity();
1004                         return;
1005                     }
1006                     mServiceHandler.processPendingTransaction(null, settings);
1007                 } else {
1008                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
1009                         Log.v(TAG, "   TYPE_MOBILE_MMS not connected, bail");
1010                     }
1011 
1012                     // Retry mms connectivity once it's possible to connect
1013                     if (mmsNetworkInfo.isAvailable()) {
1014                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
1015                             Log.v(TAG, "   retrying mms connectivity for it's available");
1016                         }
1017                         renewMmsConnectivity();
1018                     }
1019                 }
1020             }
1021         }
1022     };
1023 }
1024