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