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