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