1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mms.transaction; 19 20 import com.android.common.NetworkConnectivityListener; 21 import com.android.mms.R; 22 import com.android.mms.LogTag; 23 import com.android.mms.util.RateController; 24 import com.google.android.mms.pdu.GenericPdu; 25 import com.google.android.mms.pdu.NotificationInd; 26 import com.google.android.mms.pdu.PduHeaders; 27 import com.google.android.mms.pdu.PduParser; 28 import com.google.android.mms.pdu.PduPersister; 29 import com.android.internal.telephony.Phone; 30 import android.provider.Telephony.Mms; 31 import android.provider.Telephony.MmsSms; 32 import android.provider.Telephony.MmsSms.PendingMessages; 33 34 import android.app.Service; 35 import android.content.ContentUris; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.database.Cursor; 39 import android.net.ConnectivityManager; 40 import android.net.NetworkInfo; 41 import android.net.Uri; 42 import android.os.Handler; 43 import android.os.HandlerThread; 44 import android.os.IBinder; 45 import android.os.Looper; 46 import android.os.Message; 47 import android.os.PowerManager; 48 import android.text.TextUtils; 49 import android.util.Log; 50 import android.widget.Toast; 51 52 import java.io.IOException; 53 import java.util.ArrayList; 54 55 /** 56 * The TransactionService of the MMS Client is responsible for handling requests 57 * to initiate client-transactions sent from: 58 * <ul> 59 * <li>The Proxy-Relay (Through Push messages)</li> 60 * <li>The composer/viewer activities of the MMS Client (Through intents)</li> 61 * </ul> 62 * The TransactionService runs locally in the same process as the application. 63 * It contains a HandlerThread to which messages are posted from the 64 * intent-receivers of this application. 65 * <p/> 66 * <b>IMPORTANT</b>: This is currently the only instance in the system in 67 * which simultaneous connectivity to both the mobile data network and 68 * a Wi-Fi network is allowed. This makes the code for handling network 69 * connectivity somewhat different than it is in other applications. In 70 * particular, we want to be able to send or receive MMS messages when 71 * a Wi-Fi connection is active (which implies that there is no connection 72 * to the mobile data network). This has two main consequences: 73 * <ul> 74 * <li>Testing for current network connectivity ({@link android.net.NetworkInfo#isConnected()} is 75 * not sufficient. Instead, the correct test is for network availability 76 * ({@link android.net.NetworkInfo#isAvailable()}).</li> 77 * <li>If the mobile data network is not in the connected state, but it is available, 78 * we must initiate setup of the mobile data connection, and defer handling 79 * the MMS transaction until the connection is established.</li> 80 * </ul> 81 */ 82 public class TransactionService extends Service implements Observer { 83 private static final String TAG = "TransactionService"; 84 85 /** 86 * Used to identify notification intents broadcasted by the 87 * TransactionService when a Transaction is completed. 88 */ 89 public static final String TRANSACTION_COMPLETED_ACTION = 90 "android.intent.action.TRANSACTION_COMPLETED_ACTION"; 91 92 /** 93 * Action for the Intent which is sent by Alarm service to launch 94 * TransactionService. 95 */ 96 public static final String ACTION_ONALARM = "android.intent.action.ACTION_ONALARM"; 97 98 /** 99 * Used as extra key in notification intents broadcasted by the TransactionService 100 * when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents). 101 * Allowed values for this key are: TransactionState.INITIALIZED, 102 * TransactionState.SUCCESS, TransactionState.FAILED. 103 */ 104 public static final String STATE = "state"; 105 106 /** 107 * Used as extra key in notification intents broadcasted by the TransactionService 108 * when a Transaction is completed (TRANSACTION_COMPLETED_ACTION intents). 109 * Allowed values for this key are any valid content uri. 110 */ 111 public static final String STATE_URI = "uri"; 112 113 private static final int EVENT_TRANSACTION_REQUEST = 1; 114 private static final int EVENT_DATA_STATE_CHANGED = 2; 115 private static final int EVENT_CONTINUE_MMS_CONNECTIVITY = 3; 116 private static final int EVENT_HANDLE_NEXT_PENDING_TRANSACTION = 4; 117 private static final int EVENT_QUIT = 100; 118 119 private static final int TOAST_MSG_QUEUED = 1; 120 private static final int TOAST_DOWNLOAD_LATER = 2; 121 private static final int TOAST_NONE = -1; 122 123 // How often to extend the use of the MMS APN while a transaction 124 // is still being processed. 125 private static final int APN_EXTENSION_WAIT = 30 * 1000; 126 127 private ServiceHandler mServiceHandler; 128 private Looper mServiceLooper; 129 private final ArrayList<Transaction> mProcessing = new ArrayList<Transaction>(); 130 private final ArrayList<Transaction> mPending = new ArrayList<Transaction>(); 131 private ConnectivityManager mConnMgr; 132 private NetworkConnectivityListener mConnectivityListener; 133 private PowerManager.WakeLock mWakeLock; 134 135 public Handler mToastHandler = new Handler() { 136 @Override 137 public void handleMessage(Message msg) { 138 String str = null; 139 140 if (msg.what == TOAST_MSG_QUEUED) { 141 str = getString(R.string.message_queued); 142 } else if (msg.what == TOAST_DOWNLOAD_LATER) { 143 str = getString(R.string.download_later); 144 } 145 146 if (str != null) { 147 Toast.makeText(TransactionService.this, str, 148 Toast.LENGTH_LONG).show(); 149 } 150 } 151 }; 152 153 @Override 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 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 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 290 private static boolean isTransientFailure(int type) { 291 return (type < MmsSms.ERR_TYPE_GENERIC_PERMANENT) && (type > MmsSms.NO_ERROR); 292 } 293 294 private boolean isNetworkAvailable() { 295 return mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS). 296 isAvailable(); 297 } 298 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 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 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 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 365 public IBinder onBind(Intent intent) { 366 return null; 367 } 368 369 /** 370 * Handle status change of Transaction (The Observable). 371 */ 372 public void update(Observable observable) { 373 Transaction transaction = (Transaction) observable; 374 int serviceId = transaction.getServiceId(); 375 376 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 377 Log.v(TAG, "update transaction " + serviceId); 378 } 379 380 try { 381 synchronized (mProcessing) { 382 mProcessing.remove(transaction); 383 if (mPending.size() > 0) { 384 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 385 Log.v(TAG, "update: handle next pending transaction..."); 386 } 387 Message msg = mServiceHandler.obtainMessage( 388 EVENT_HANDLE_NEXT_PENDING_TRANSACTION, 389 transaction.getConnectionSettings()); 390 mServiceHandler.sendMessage(msg); 391 } 392 else { 393 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 394 Log.v(TAG, "update: endMmsConnectivity"); 395 } 396 endMmsConnectivity(); 397 } 398 } 399 400 Intent intent = new Intent(TRANSACTION_COMPLETED_ACTION); 401 TransactionState state = transaction.getState(); 402 int result = state.getState(); 403 intent.putExtra(STATE, result); 404 405 switch (result) { 406 case TransactionState.SUCCESS: 407 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 408 Log.v(TAG, "Transaction complete: " + serviceId); 409 } 410 411 intent.putExtra(STATE_URI, state.getContentUri()); 412 413 // Notify user in the system-wide notification area. 414 switch (transaction.getType()) { 415 case Transaction.NOTIFICATION_TRANSACTION: 416 case Transaction.RETRIEVE_TRANSACTION: 417 // We're already in a non-UI thread called from 418 // NotificationTransacation.run(), so ok to block here. 419 MessagingNotification.blockingUpdateNewMessageIndicator(this, true, 420 false); 421 MessagingNotification.updateDownloadFailedNotification(this); 422 break; 423 case Transaction.SEND_TRANSACTION: 424 RateController.getInstance().update(); 425 break; 426 } 427 break; 428 case TransactionState.FAILED: 429 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 430 Log.v(TAG, "Transaction failed: " + serviceId); 431 } 432 break; 433 default: 434 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 435 Log.v(TAG, "Transaction state unknown: " + 436 serviceId + " " + result); 437 } 438 break; 439 } 440 441 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 442 Log.v(TAG, "update: broadcast transaction result " + result); 443 } 444 // Broadcast the result of the transaction. 445 sendBroadcast(intent); 446 } finally { 447 transaction.detach(this); 448 MmsSystemEventReceiver.unRegisterForConnectionStateChanges(getApplicationContext()); 449 stopSelf(serviceId); 450 } 451 } 452 453 private synchronized void createWakeLock() { 454 // Create a new wake lock if we haven't made one yet. 455 if (mWakeLock == null) { 456 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 457 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity"); 458 mWakeLock.setReferenceCounted(false); 459 } 460 } 461 462 private void acquireWakeLock() { 463 // It's okay to double-acquire this because we are not using it 464 // in reference-counted mode. 465 mWakeLock.acquire(); 466 } 467 468 private void releaseWakeLock() { 469 // Don't release the wake lock if it hasn't been created and acquired. 470 if (mWakeLock != null && mWakeLock.isHeld()) { 471 mWakeLock.release(); 472 } 473 } 474 475 protected int beginMmsConnectivity() throws IOException { 476 // Take a wake lock so we don't fall asleep before the message is downloaded. 477 createWakeLock(); 478 479 int result = mConnMgr.startUsingNetworkFeature( 480 ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS); 481 482 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 483 Log.v(TAG, "beginMmsConnectivity: result=" + result); 484 } 485 486 switch (result) { 487 case Phone.APN_ALREADY_ACTIVE: 488 case Phone.APN_REQUEST_STARTED: 489 acquireWakeLock(); 490 return result; 491 } 492 493 throw new IOException("Cannot establish MMS connectivity"); 494 } 495 496 protected void endMmsConnectivity() { 497 try { 498 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 499 Log.v(TAG, "endMmsConnectivity"); 500 } 501 502 // cancel timer for renewal of lease 503 mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY); 504 if (mConnMgr != null) { 505 mConnMgr.stopUsingNetworkFeature( 506 ConnectivityManager.TYPE_MOBILE, 507 Phone.FEATURE_ENABLE_MMS); 508 } 509 } finally { 510 releaseWakeLock(); 511 } 512 } 513 514 private final class ServiceHandler extends Handler { 515 public ServiceHandler(Looper looper) { 516 super(looper); 517 } 518 519 /** 520 * Handle incoming transaction requests. 521 * The incoming requests are initiated by the MMSC Server or by the 522 * MMS Client itself. 523 */ 524 @Override 525 public void handleMessage(Message msg) { 526 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 527 Log.v(TAG, "Handling incoming message: " + msg); 528 } 529 530 Transaction transaction = null; 531 532 switch (msg.what) { 533 case EVENT_QUIT: 534 getLooper().quit(); 535 return; 536 537 case EVENT_CONTINUE_MMS_CONNECTIVITY: 538 synchronized (mProcessing) { 539 if (mProcessing.isEmpty()) { 540 return; 541 } 542 } 543 544 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 545 Log.v(TAG, "handle EVENT_CONTINUE_MMS_CONNECTIVITY event..."); 546 } 547 548 try { 549 int result = beginMmsConnectivity(); 550 if (result != Phone.APN_ALREADY_ACTIVE) { 551 Log.v(TAG, "Extending MMS connectivity returned " + result + 552 " instead of APN_ALREADY_ACTIVE"); 553 // Just wait for connectivity startup without 554 // any new request of APN switch. 555 return; 556 } 557 } catch (IOException e) { 558 Log.w(TAG, "Attempt to extend use of MMS connectivity failed"); 559 return; 560 } 561 562 // Restart timer 563 sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 564 APN_EXTENSION_WAIT); 565 return; 566 567 case EVENT_DATA_STATE_CHANGED: 568 /* 569 * If we are being informed that connectivity has been established 570 * to allow MMS traffic, then proceed with processing the pending 571 * transaction, if any. 572 */ 573 if (mConnectivityListener == null) { 574 return; 575 } 576 577 NetworkInfo info = mConnectivityListener.getNetworkInfo(); 578 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 579 Log.v(TAG, "Handle DATA_STATE_CHANGED event: " + info); 580 } 581 582 // Check availability of the mobile network. 583 if ((info == null) || (info.getType() != 584 ConnectivityManager.TYPE_MOBILE_MMS)) { 585 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 586 Log.v(TAG, " type is not TYPE_MOBILE_MMS, bail"); 587 } 588 return; 589 } 590 591 if (!info.isConnected()) { 592 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 593 Log.v(TAG, " TYPE_MOBILE_MMS not connected, bail"); 594 } 595 return; 596 } 597 598 TransactionSettings settings = new TransactionSettings( 599 TransactionService.this, info.getExtraInfo()); 600 601 // If this APN doesn't have an MMSC, wait for one that does. 602 if (TextUtils.isEmpty(settings.getMmscUrl())) { 603 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 604 Log.v(TAG, " empty MMSC url, bail"); 605 } 606 return; 607 } 608 609 // Set a timer to keep renewing our "lease" on the MMS connection 610 sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 611 APN_EXTENSION_WAIT); 612 processPendingTransaction(transaction, settings); 613 return; 614 615 case EVENT_TRANSACTION_REQUEST: 616 int serviceId = msg.arg1; 617 try { 618 TransactionBundle args = (TransactionBundle) msg.obj; 619 TransactionSettings transactionSettings; 620 621 // Set the connection settings for this transaction. 622 // If these have not been set in args, load the default settings. 623 String mmsc = args.getMmscUrl(); 624 if (mmsc != null) { 625 transactionSettings = new TransactionSettings( 626 mmsc, args.getProxyAddress(), args.getProxyPort()); 627 } else { 628 transactionSettings = new TransactionSettings( 629 TransactionService.this, null); 630 } 631 632 int transactionType = args.getTransactionType(); 633 634 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 635 Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" + 636 transactionType); 637 } 638 639 // Create appropriate transaction 640 switch (transactionType) { 641 case Transaction.NOTIFICATION_TRANSACTION: 642 String uri = args.getUri(); 643 if (uri != null) { 644 transaction = new NotificationTransaction( 645 TransactionService.this, serviceId, 646 transactionSettings, uri); 647 } else { 648 // Now it's only used for test purpose. 649 byte[] pushData = args.getPushData(); 650 PduParser parser = new PduParser(pushData); 651 GenericPdu ind = parser.parse(); 652 653 int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND; 654 if ((ind != null) && (ind.getMessageType() == type)) { 655 transaction = new NotificationTransaction( 656 TransactionService.this, serviceId, 657 transactionSettings, (NotificationInd) ind); 658 } else { 659 Log.e(TAG, "Invalid PUSH data."); 660 transaction = null; 661 return; 662 } 663 } 664 break; 665 case Transaction.RETRIEVE_TRANSACTION: 666 transaction = new RetrieveTransaction( 667 TransactionService.this, serviceId, 668 transactionSettings, args.getUri()); 669 break; 670 case Transaction.SEND_TRANSACTION: 671 transaction = new SendTransaction( 672 TransactionService.this, serviceId, 673 transactionSettings, args.getUri()); 674 break; 675 case Transaction.READREC_TRANSACTION: 676 transaction = new ReadRecTransaction( 677 TransactionService.this, serviceId, 678 transactionSettings, args.getUri()); 679 break; 680 default: 681 Log.w(TAG, "Invalid transaction type: " + serviceId); 682 transaction = null; 683 return; 684 } 685 686 if (!processTransaction(transaction)) { 687 transaction = null; 688 return; 689 } 690 691 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 692 Log.v(TAG, "Started processing of incoming message: " + msg); 693 } 694 } catch (Exception ex) { 695 Log.w(TAG, "Exception occurred while handling message: " + msg, ex); 696 697 if (transaction != null) { 698 try { 699 transaction.detach(TransactionService.this); 700 if (mProcessing.contains(transaction)) { 701 synchronized (mProcessing) { 702 mProcessing.remove(transaction); 703 } 704 } 705 } catch (Throwable t) { 706 Log.e(TAG, "Unexpected Throwable.", t); 707 } finally { 708 // Set transaction to null to allow stopping the 709 // transaction service. 710 transaction = null; 711 } 712 } 713 } finally { 714 if (transaction == null) { 715 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 716 Log.v(TAG, "Transaction was null. Stopping self: " + serviceId); 717 } 718 endMmsConnectivity(); 719 stopSelf(serviceId); 720 } 721 } 722 return; 723 case EVENT_HANDLE_NEXT_PENDING_TRANSACTION: 724 processPendingTransaction(transaction, (TransactionSettings) msg.obj); 725 return; 726 default: 727 Log.w(TAG, "what=" + msg.what); 728 return; 729 } 730 } 731 732 private void processPendingTransaction(Transaction transaction, 733 TransactionSettings settings) { 734 735 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 736 Log.v(TAG, "processPendingTxn: transaction=" + transaction); 737 } 738 739 int numProcessTransaction = 0; 740 synchronized (mProcessing) { 741 if (mPending.size() != 0) { 742 transaction = mPending.remove(0); 743 } 744 numProcessTransaction = mProcessing.size(); 745 } 746 747 if (transaction != null) { 748 if (settings != null) { 749 transaction.setConnectionSettings(settings); 750 } 751 752 /* 753 * Process deferred transaction 754 */ 755 try { 756 int serviceId = transaction.getServiceId(); 757 758 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 759 Log.v(TAG, "processPendingTxn: process " + serviceId); 760 } 761 762 if (processTransaction(transaction)) { 763 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 764 Log.v(TAG, "Started deferred processing of transaction " 765 + transaction); 766 } 767 } else { 768 transaction = null; 769 stopSelf(serviceId); 770 } 771 } catch (IOException e) { 772 Log.w(TAG, e.getMessage(), e); 773 } 774 } else { 775 if (numProcessTransaction == 0) { 776 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 777 Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity"); 778 } 779 endMmsConnectivity(); 780 } 781 } 782 } 783 784 /** 785 * Internal method to begin processing a transaction. 786 * @param transaction the transaction. Must not be {@code null}. 787 * @return {@code true} if process has begun or will begin. {@code false} 788 * if the transaction should be discarded. 789 * @throws IOException if connectivity for MMS traffic could not be 790 * established. 791 */ 792 private boolean processTransaction(Transaction transaction) throws IOException { 793 // Check if transaction already processing 794 synchronized (mProcessing) { 795 for (Transaction t : mPending) { 796 if (t.isEquivalent(transaction)) { 797 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 798 Log.v(TAG, "Transaction already pending: " + 799 transaction.getServiceId()); 800 } 801 return true; 802 } 803 } 804 for (Transaction t : mProcessing) { 805 if (t.isEquivalent(transaction)) { 806 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 807 Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId()); 808 } 809 return true; 810 } 811 } 812 813 /* 814 * Make sure that the network connectivity necessary 815 * for MMS traffic is enabled. If it is not, we need 816 * to defer processing the transaction until 817 * connectivity is established. 818 */ 819 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 820 Log.v(TAG, "processTransaction: call beginMmsConnectivity..."); 821 } 822 int connectivityResult = beginMmsConnectivity(); 823 if (connectivityResult == Phone.APN_REQUEST_STARTED) { 824 mPending.add(transaction); 825 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 826 Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " + 827 "defer transaction pending MMS connectivity"); 828 } 829 return true; 830 } 831 832 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 833 Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction); 834 } 835 mProcessing.add(transaction); 836 } 837 838 // Set a timer to keep renewing our "lease" on the MMS connection 839 sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 840 APN_EXTENSION_WAIT); 841 842 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 843 Log.v(TAG, "processTransaction: starting transaction " + transaction); 844 } 845 846 // Attach to transaction and process it 847 transaction.attach(TransactionService.this); 848 transaction.process(); 849 return true; 850 } 851 } 852 } 853