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