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