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