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