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