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