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