• 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.internal.telephony.Phone;
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 
30 import android.app.Service;
31 import android.content.ContentUris;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.database.Cursor;
35 import android.net.ConnectivityManager;
36 import android.net.NetworkConnectivityListener;
37 import android.net.NetworkInfo;
38 import android.net.Uri;
39 import android.os.Handler;
40 import android.os.HandlerThread;
41 import android.os.IBinder;
42 import android.os.Looper;
43 import android.os.Message;
44 import android.os.PowerManager;
45 import android.provider.Telephony.Mms;
46 import android.provider.Telephony.MmsSms;
47 import android.provider.Telephony.MmsSms.PendingMessages;
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                             MessagingNotification.updateNewMessageIndicator(this, true);
418                             MessagingNotification.updateDownloadFailedNotification(this);
419                             break;
420                         case Transaction.SEND_TRANSACTION:
421                             RateController.getInstance().update();
422                             break;
423                     }
424                     break;
425                 case TransactionState.FAILED:
426                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
427                         Log.v(TAG, "Transaction failed: " + serviceId);
428                     }
429                     break;
430                 default:
431                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
432                         Log.v(TAG, "Transaction state unknown: " +
433                                 serviceId + " " + result);
434                     }
435                     break;
436             }
437 
438             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
439                 Log.v(TAG, "update: broadcast transaction result " + result);
440             }
441             // Broadcast the result of the transaction.
442             sendBroadcast(intent);
443         } finally {
444             transaction.detach(this);
445             MmsSystemEventReceiver.unRegisterForConnectionStateChanges(getApplicationContext());
446             stopSelf(serviceId);
447         }
448     }
449 
createWakeLock()450     private synchronized void createWakeLock() {
451         // Create a new wake lock if we haven't made one yet.
452         if (mWakeLock == null) {
453             PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
454             mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity");
455             mWakeLock.setReferenceCounted(false);
456         }
457     }
458 
acquireWakeLock()459     private void acquireWakeLock() {
460         // It's okay to double-acquire this because we are not using it
461         // in reference-counted mode.
462         mWakeLock.acquire();
463     }
464 
releaseWakeLock()465     private void releaseWakeLock() {
466         // Don't release the wake lock if it hasn't been created and acquired.
467         if (mWakeLock != null && mWakeLock.isHeld()) {
468             mWakeLock.release();
469         }
470     }
471 
beginMmsConnectivity()472     protected int beginMmsConnectivity() throws IOException {
473         // Take a wake lock so we don't fall asleep before the message is downloaded.
474         createWakeLock();
475 
476         int result = mConnMgr.startUsingNetworkFeature(
477                 ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS);
478 
479         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
480             Log.v(TAG, "beginMmsConnectivity: result=" + result);
481         }
482 
483         switch (result) {
484             case Phone.APN_ALREADY_ACTIVE:
485             case Phone.APN_REQUEST_STARTED:
486                 acquireWakeLock();
487                 return result;
488         }
489 
490         throw new IOException("Cannot establish MMS connectivity");
491     }
492 
endMmsConnectivity()493     protected void endMmsConnectivity() {
494         try {
495             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
496                 Log.v(TAG, "endMmsConnectivity");
497             }
498 
499             // cancel timer for renewal of lease
500             mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY);
501             if (mConnMgr != null) {
502                 mConnMgr.stopUsingNetworkFeature(
503                         ConnectivityManager.TYPE_MOBILE,
504                         Phone.FEATURE_ENABLE_MMS);
505             }
506         } finally {
507             releaseWakeLock();
508         }
509     }
510 
511     private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper)512         public ServiceHandler(Looper looper) {
513             super(looper);
514         }
515 
516         /**
517          * Handle incoming transaction requests.
518          * The incoming requests are initiated by the MMSC Server or by the
519          * MMS Client itself.
520          */
521         @Override
handleMessage(Message msg)522         public void handleMessage(Message msg) {
523             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
524                 Log.v(TAG, "Handling incoming message: " + msg);
525             }
526 
527             Transaction transaction = null;
528 
529             switch (msg.what) {
530                 case EVENT_QUIT:
531                     getLooper().quit();
532                     return;
533 
534                 case EVENT_CONTINUE_MMS_CONNECTIVITY:
535                     synchronized (mProcessing) {
536                         if (mProcessing.isEmpty()) {
537                             return;
538                         }
539                     }
540 
541                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
542                         Log.v(TAG, "handle EVENT_CONTINUE_MMS_CONNECTIVITY event...");
543                     }
544 
545                     try {
546                         int result = beginMmsConnectivity();
547                         if (result != Phone.APN_ALREADY_ACTIVE) {
548                             Log.v(TAG, "Extending MMS connectivity returned " + result +
549                                     " instead of APN_ALREADY_ACTIVE");
550                             // Just wait for connectivity startup without
551                             // any new request of APN switch.
552                             return;
553                         }
554                     } catch (IOException e) {
555                         Log.w(TAG, "Attempt to extend use of MMS connectivity failed");
556                         return;
557                     }
558 
559                     // Restart timer
560                     sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
561                                        APN_EXTENSION_WAIT);
562                     return;
563 
564                 case EVENT_DATA_STATE_CHANGED:
565                     /*
566                      * If we are being informed that connectivity has been established
567                      * to allow MMS traffic, then proceed with processing the pending
568                      * transaction, if any.
569                      */
570                     if (mConnectivityListener == null) {
571                         return;
572                     }
573 
574                     NetworkInfo info = mConnectivityListener.getNetworkInfo();
575                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
576                         Log.v(TAG, "Handle DATA_STATE_CHANGED event: " + info);
577                     }
578 
579                     // Check availability of the mobile network.
580                     if ((info == null) || (info.getType() !=
581                             ConnectivityManager.TYPE_MOBILE_MMS)) {
582                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
583                             Log.v(TAG, "   type is not TYPE_MOBILE_MMS, bail");
584                         }
585                         return;
586                     }
587 
588                     if (!info.isConnected()) {
589                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
590                             Log.v(TAG, "   TYPE_MOBILE_MMS not connected, bail");
591                         }
592                         return;
593                     }
594 
595                     TransactionSettings settings = new TransactionSettings(
596                             TransactionService.this, info.getExtraInfo());
597 
598                     // If this APN doesn't have an MMSC, wait for one that does.
599                     if (TextUtils.isEmpty(settings.getMmscUrl())) {
600                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
601                             Log.v(TAG, "   empty MMSC url, bail");
602                         }
603                         return;
604                     }
605 
606                     // Set a timer to keep renewing our "lease" on the MMS connection
607                     sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
608                                        APN_EXTENSION_WAIT);
609                     processPendingTransaction(transaction, settings);
610                     return;
611 
612                 case EVENT_TRANSACTION_REQUEST:
613                     int serviceId = msg.arg1;
614                     try {
615                         TransactionBundle args = (TransactionBundle) msg.obj;
616                         TransactionSettings transactionSettings;
617 
618                         // Set the connection settings for this transaction.
619                         // If these have not been set in args, load the default settings.
620                         String mmsc = args.getMmscUrl();
621                         if (mmsc != null) {
622                             transactionSettings = new TransactionSettings(
623                                     mmsc, args.getProxyAddress(), args.getProxyPort());
624                         } else {
625                             transactionSettings = new TransactionSettings(
626                                                     TransactionService.this, null);
627                         }
628 
629                         int transactionType = args.getTransactionType();
630 
631                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
632                             Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" +
633                                     transactionType);
634                         }
635 
636                         // Create appropriate transaction
637                         switch (transactionType) {
638                             case Transaction.NOTIFICATION_TRANSACTION:
639                                 String uri = args.getUri();
640                                 if (uri != null) {
641                                     transaction = new NotificationTransaction(
642                                             TransactionService.this, serviceId,
643                                             transactionSettings, uri);
644                                 } else {
645                                     // Now it's only used for test purpose.
646                                     byte[] pushData = args.getPushData();
647                                     PduParser parser = new PduParser(pushData);
648                                     GenericPdu ind = parser.parse();
649 
650                                     int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
651                                     if ((ind != null) && (ind.getMessageType() == type)) {
652                                         transaction = new NotificationTransaction(
653                                                 TransactionService.this, serviceId,
654                                                 transactionSettings, (NotificationInd) ind);
655                                     } else {
656                                         Log.e(TAG, "Invalid PUSH data.");
657                                         transaction = null;
658                                         return;
659                                     }
660                                 }
661                                 break;
662                             case Transaction.RETRIEVE_TRANSACTION:
663                                 transaction = new RetrieveTransaction(
664                                         TransactionService.this, serviceId,
665                                         transactionSettings, args.getUri());
666                                 break;
667                             case Transaction.SEND_TRANSACTION:
668                                 transaction = new SendTransaction(
669                                         TransactionService.this, serviceId,
670                                         transactionSettings, args.getUri());
671                                 break;
672                             case Transaction.READREC_TRANSACTION:
673                                 transaction = new ReadRecTransaction(
674                                         TransactionService.this, serviceId,
675                                         transactionSettings, args.getUri());
676                                 break;
677                             default:
678                                 Log.w(TAG, "Invalid transaction type: " + serviceId);
679                                 transaction = null;
680                                 return;
681                         }
682 
683                         if (!processTransaction(transaction)) {
684                             transaction = null;
685                             return;
686                         }
687 
688                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
689                             Log.v(TAG, "Started processing of incoming message: " + msg);
690                         }
691                     } catch (Exception ex) {
692                         Log.w(TAG, "Exception occurred while handling message: " + msg, ex);
693 
694                         if (transaction != null) {
695                             try {
696                                 transaction.detach(TransactionService.this);
697                                 if (mProcessing.contains(transaction)) {
698                                     synchronized (mProcessing) {
699                                         mProcessing.remove(transaction);
700                                     }
701                                 }
702                             } catch (Throwable t) {
703                                 Log.e(TAG, "Unexpected Throwable.", t);
704                             } finally {
705                                 // Set transaction to null to allow stopping the
706                                 // transaction service.
707                                 transaction = null;
708                             }
709                         }
710                     } finally {
711                         if (transaction == null) {
712                             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
713                                 Log.v(TAG, "Transaction was null. Stopping self: " + serviceId);
714                             }
715                             endMmsConnectivity();
716                             stopSelf(serviceId);
717                         }
718                     }
719                     return;
720                 case EVENT_HANDLE_NEXT_PENDING_TRANSACTION:
721                     processPendingTransaction(transaction, (TransactionSettings) msg.obj);
722                     return;
723                 default:
724                     Log.w(TAG, "what=" + msg.what);
725                     return;
726             }
727         }
728 
processPendingTransaction(Transaction transaction, TransactionSettings settings)729         private void processPendingTransaction(Transaction transaction,
730                                                TransactionSettings settings) {
731 
732             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
733                 Log.v(TAG, "processPendingTxn: transaction=" + transaction);
734             }
735 
736             int numProcessTransaction = 0;
737             synchronized (mProcessing) {
738                 if (mPending.size() != 0) {
739                     transaction = mPending.remove(0);
740                 }
741                 numProcessTransaction = mProcessing.size();
742             }
743 
744             if (transaction != null) {
745                 if (settings != null) {
746                     transaction.setConnectionSettings(settings);
747                 }
748 
749                 /*
750                  * Process deferred transaction
751                  */
752                 try {
753                     int serviceId = transaction.getServiceId();
754 
755                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
756                         Log.v(TAG, "processPendingTxn: process " + serviceId);
757                     }
758 
759                     if (processTransaction(transaction)) {
760                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
761                             Log.v(TAG, "Started deferred processing of transaction  "
762                                     + transaction);
763                         }
764                     } else {
765                         transaction = null;
766                         stopSelf(serviceId);
767                     }
768                 } catch (IOException e) {
769                     Log.w(TAG, e.getMessage(), e);
770                 }
771             } else {
772                 if (numProcessTransaction == 0) {
773                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
774                         Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity");
775                     }
776                     endMmsConnectivity();
777                 }
778             }
779         }
780 
781         /**
782          * Internal method to begin processing a transaction.
783          * @param transaction the transaction. Must not be {@code null}.
784          * @return {@code true} if process has begun or will begin. {@code false}
785          * if the transaction should be discarded.
786          * @throws IOException if connectivity for MMS traffic could not be
787          * established.
788          */
processTransaction(Transaction transaction)789         private boolean processTransaction(Transaction transaction) throws IOException {
790             // Check if transaction already processing
791             synchronized (mProcessing) {
792                 for (Transaction t : mPending) {
793                     if (t.isEquivalent(transaction)) {
794                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
795                             Log.v(TAG, "Transaction already pending: " +
796                                     transaction.getServiceId());
797                         }
798                         return true;
799                     }
800                 }
801                 for (Transaction t : mProcessing) {
802                     if (t.isEquivalent(transaction)) {
803                         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
804                             Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId());
805                         }
806                         return true;
807                     }
808                 }
809 
810                 /*
811                 * Make sure that the network connectivity necessary
812                 * for MMS traffic is enabled. If it is not, we need
813                 * to defer processing the transaction until
814                 * connectivity is established.
815                 */
816                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
817                     Log.v(TAG, "processTransaction: call beginMmsConnectivity...");
818                 }
819                 int connectivityResult = beginMmsConnectivity();
820                 if (connectivityResult == Phone.APN_REQUEST_STARTED) {
821                     mPending.add(transaction);
822                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
823                         Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " +
824                                 "defer transaction pending MMS connectivity");
825                     }
826                     return true;
827                 }
828 
829                 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
830                     Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction);
831                 }
832                 mProcessing.add(transaction);
833             }
834 
835             // Set a timer to keep renewing our "lease" on the MMS connection
836             sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY),
837                                APN_EXTENSION_WAIT);
838 
839             if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
840                 Log.v(TAG, "processTransaction: starting transaction " + transaction);
841             }
842 
843             // Attach to transaction and process it
844             transaction.attach(TransactionService.this);
845             transaction.process();
846             return true;
847         }
848     }
849 }
850