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 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 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 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 294 private static boolean isTransientFailure(int type) { 295 return (type < MmsSms.ERR_TYPE_GENERIC_PERMANENT) && (type > MmsSms.NO_ERROR); 296 } 297 298 private boolean isNetworkAvailable() { 299 NetworkInfo ni = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); 300 return (ni == null ? false : ni.isAvailable()); 301 } 302 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 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 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 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 367 public IBinder onBind(Intent intent) { 368 return null; 369 } 370 371 /** 372 * Handle status change of Transaction (The Observable). 373 */ 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 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 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 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 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 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 { 517 public ServiceHandler(Looper looper) { 518 super(looper); 519 } 520 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 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 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 renewMmsConnectivity(); 592 return; 593 594 case EVENT_TRANSACTION_REQUEST: 595 int serviceId = msg.arg1; 596 try { 597 TransactionBundle args = (TransactionBundle) msg.obj; 598 TransactionSettings transactionSettings; 599 600 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 601 Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" + 602 args.getMmscUrl() + " proxy port: " + args.getProxyAddress()); 603 } 604 605 // Set the connection settings for this transaction. 606 // If these have not been set in args, load the default settings. 607 String mmsc = args.getMmscUrl(); 608 if (mmsc != null) { 609 transactionSettings = new TransactionSettings( 610 mmsc, args.getProxyAddress(), args.getProxyPort()); 611 } else { 612 transactionSettings = new TransactionSettings( 613 TransactionService.this, null); 614 } 615 616 int transactionType = args.getTransactionType(); 617 618 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 619 Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" + 620 transactionType + " " + decodeTransactionType(transactionType)); 621 } 622 623 // Create appropriate transaction 624 switch (transactionType) { 625 case Transaction.NOTIFICATION_TRANSACTION: 626 String uri = args.getUri(); 627 if (uri != null) { 628 transaction = new NotificationTransaction( 629 TransactionService.this, serviceId, 630 transactionSettings, uri); 631 } else { 632 // Now it's only used for test purpose. 633 byte[] pushData = args.getPushData(); 634 PduParser parser = new PduParser(pushData); 635 GenericPdu ind = parser.parse(); 636 637 int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND; 638 if ((ind != null) && (ind.getMessageType() == type)) { 639 transaction = new NotificationTransaction( 640 TransactionService.this, serviceId, 641 transactionSettings, (NotificationInd) ind); 642 } else { 643 Log.e(TAG, "Invalid PUSH data."); 644 transaction = null; 645 return; 646 } 647 } 648 break; 649 case Transaction.RETRIEVE_TRANSACTION: 650 transaction = new RetrieveTransaction( 651 TransactionService.this, serviceId, 652 transactionSettings, args.getUri()); 653 break; 654 case Transaction.SEND_TRANSACTION: 655 transaction = new SendTransaction( 656 TransactionService.this, serviceId, 657 transactionSettings, args.getUri()); 658 break; 659 case Transaction.READREC_TRANSACTION: 660 transaction = new ReadRecTransaction( 661 TransactionService.this, serviceId, 662 transactionSettings, args.getUri()); 663 break; 664 default: 665 Log.w(TAG, "Invalid transaction type: " + serviceId); 666 transaction = null; 667 return; 668 } 669 670 if (!processTransaction(transaction)) { 671 transaction = null; 672 return; 673 } 674 675 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 676 Log.v(TAG, "Started processing of incoming message: " + msg); 677 } 678 } catch (Exception ex) { 679 Log.w(TAG, "Exception occurred while handling message: " + msg, ex); 680 681 if (transaction != null) { 682 try { 683 transaction.detach(TransactionService.this); 684 if (mProcessing.contains(transaction)) { 685 synchronized (mProcessing) { 686 mProcessing.remove(transaction); 687 } 688 } 689 } catch (Throwable t) { 690 Log.e(TAG, "Unexpected Throwable.", t); 691 } finally { 692 // Set transaction to null to allow stopping the 693 // transaction service. 694 transaction = null; 695 } 696 } 697 } finally { 698 if (transaction == null) { 699 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 700 Log.v(TAG, "Transaction was null. Stopping self: " + serviceId); 701 } 702 endMmsConnectivity(); 703 stopSelf(serviceId); 704 } 705 } 706 return; 707 case EVENT_HANDLE_NEXT_PENDING_TRANSACTION: 708 processPendingTransaction(transaction, (TransactionSettings) msg.obj); 709 return; 710 default: 711 Log.w(TAG, "what=" + msg.what); 712 return; 713 } 714 } 715 716 public void processPendingTransaction(Transaction transaction, 717 TransactionSettings settings) { 718 719 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 720 Log.v(TAG, "processPendingTxn: transaction=" + transaction); 721 } 722 723 int numProcessTransaction = 0; 724 synchronized (mProcessing) { 725 if (mPending.size() != 0) { 726 transaction = mPending.remove(0); 727 } 728 numProcessTransaction = mProcessing.size(); 729 } 730 731 if (transaction != null) { 732 if (settings != null) { 733 transaction.setConnectionSettings(settings); 734 } 735 736 /* 737 * Process deferred transaction 738 */ 739 try { 740 int serviceId = transaction.getServiceId(); 741 742 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 743 Log.v(TAG, "processPendingTxn: process " + serviceId); 744 } 745 746 if (processTransaction(transaction)) { 747 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 748 Log.v(TAG, "Started deferred processing of transaction " 749 + transaction); 750 } 751 } else { 752 transaction = null; 753 stopSelf(serviceId); 754 } 755 } catch (IOException e) { 756 Log.w(TAG, e.getMessage(), e); 757 } 758 } else { 759 if (numProcessTransaction == 0) { 760 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 761 Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity"); 762 } 763 endMmsConnectivity(); 764 } 765 } 766 } 767 768 /** 769 * Internal method to begin processing a transaction. 770 * @param transaction the transaction. Must not be {@code null}. 771 * @return {@code true} if process has begun or will begin. {@code false} 772 * if the transaction should be discarded. 773 * @throws IOException if connectivity for MMS traffic could not be 774 * established. 775 */ 776 private boolean processTransaction(Transaction transaction) throws IOException { 777 // Check if transaction already processing 778 synchronized (mProcessing) { 779 for (Transaction t : mPending) { 780 if (t.isEquivalent(transaction)) { 781 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 782 Log.v(TAG, "Transaction already pending: " + 783 transaction.getServiceId()); 784 } 785 return true; 786 } 787 } 788 for (Transaction t : mProcessing) { 789 if (t.isEquivalent(transaction)) { 790 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 791 Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId()); 792 } 793 return true; 794 } 795 } 796 797 /* 798 * Make sure that the network connectivity necessary 799 * for MMS traffic is enabled. If it is not, we need 800 * to defer processing the transaction until 801 * connectivity is established. 802 */ 803 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 804 Log.v(TAG, "processTransaction: call beginMmsConnectivity..."); 805 } 806 int connectivityResult = beginMmsConnectivity(); 807 if (connectivityResult == Phone.APN_REQUEST_STARTED) { 808 mPending.add(transaction); 809 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 810 Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " + 811 "defer transaction pending MMS connectivity"); 812 } 813 return true; 814 } 815 816 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 817 Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction); 818 } 819 mProcessing.add(transaction); 820 } 821 822 // Set a timer to keep renewing our "lease" on the MMS connection 823 sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 824 APN_EXTENSION_WAIT); 825 826 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 827 Log.v(TAG, "processTransaction: starting transaction " + transaction); 828 } 829 830 // Attach to transaction and process it 831 transaction.attach(TransactionService.this); 832 transaction.process(); 833 return true; 834 } 835 } 836 837 private void renewMmsConnectivity() { 838 // Set a timer to keep renewing our "lease" on the MMS connection 839 mServiceHandler.sendMessageDelayed( 840 mServiceHandler.obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 841 APN_EXTENSION_WAIT); 842 } 843 844 private class ConnectivityBroadcastReceiver extends BroadcastReceiver { 845 @Override 846 public void onReceive(Context context, Intent intent) { 847 String action = intent.getAction(); 848 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 849 Log.w(TAG, "ConnectivityBroadcastReceiver.onReceive() action: " + action); 850 } 851 852 if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 853 return; 854 } 855 856 boolean noConnectivity = 857 intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); 858 859 NetworkInfo networkInfo = (NetworkInfo) 860 intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); 861 862 /* 863 * If we are being informed that connectivity has been established 864 * to allow MMS traffic, then proceed with processing the pending 865 * transaction, if any. 866 */ 867 868 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 869 Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + networkInfo); 870 } 871 872 // Check availability of the mobile network. 873 if ((networkInfo == null) || (networkInfo.getType() != 874 ConnectivityManager.TYPE_MOBILE_MMS)) { 875 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 876 Log.v(TAG, " type is not TYPE_MOBILE_MMS, bail"); 877 } 878 // This is a very specific fix to handle the case where the phone receives an 879 // incoming call during the time we're trying to setup the mms connection. 880 // When the call ends, restart the process of mms connectivity. 881 if (networkInfo != null && 882 Phone.REASON_VOICE_CALL_ENDED.equals(networkInfo.getReason())) { 883 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 884 Log.v(TAG, " reason is " + Phone.REASON_VOICE_CALL_ENDED + 885 ", retrying mms connectivity"); 886 } 887 renewMmsConnectivity(); 888 } 889 return; 890 } 891 892 if (!networkInfo.isConnected()) { 893 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 894 Log.v(TAG, " TYPE_MOBILE_MMS not connected, bail"); 895 } 896 return; 897 } 898 899 TransactionSettings settings = new TransactionSettings( 900 TransactionService.this, networkInfo.getExtraInfo()); 901 902 // If this APN doesn't have an MMSC, wait for one that does. 903 if (TextUtils.isEmpty(settings.getMmscUrl())) { 904 Log.v(TAG, " empty MMSC url, bail"); 905 return; 906 } 907 908 renewMmsConnectivity(); 909 mServiceHandler.processPendingTransaction(null, settings); 910 } 911 }; 912 } 913