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