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 = LogTag.TAG; 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 // Re-enable "download" button if auto-download is off 284 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, 285 cursor.getLong(columnIndexOfMsgId)); 286 downloadManager.markState(uri, 287 DownloadManager.STATE_SKIP_RETRYING); 288 break; 289 } 290 // Logic is twisty. If there's no failure or the failure 291 // is a non-permanent failure, we want to process the transaction. 292 // Otherwise, break out and skip processing this transaction. 293 if (!(failureType == MmsSms.NO_ERROR || 294 isTransientFailure(failureType))) { 295 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 296 Log.v(TAG, "onNewIntent: skipping - permanent error"); 297 } 298 break; 299 } 300 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 301 Log.v(TAG, "onNewIntent: falling through and processing"); 302 } 303 // fall-through 304 default: 305 Uri uri = ContentUris.withAppendedId( 306 Mms.CONTENT_URI, 307 cursor.getLong(columnIndexOfMsgId)); 308 TransactionBundle args = new TransactionBundle( 309 transactionType, uri.toString()); 310 // FIXME: We use the same startId for all MMs. 311 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 312 Log.v(TAG, "onNewIntent: launchTransaction uri=" + uri); 313 } 314 launchTransaction(serviceId, args, false); 315 break; 316 } 317 } 318 } finally { 319 cursor.close(); 320 } 321 } else { 322 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 323 Log.v(TAG, "onNewIntent: no pending messages. Stopping service."); 324 } 325 RetryScheduler.setRetryAlarm(this); 326 stopSelfIfIdle(serviceId); 327 } 328 } else { 329 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 330 Log.v(TAG, "onNewIntent: launch transaction..."); 331 } 332 // For launching NotificationTransaction and test purpose. 333 TransactionBundle args = new TransactionBundle(intent.getExtras()); 334 launchTransaction(serviceId, args, noNetwork); 335 } 336 } 337 stopSelfIfIdle(int startId)338 private void stopSelfIfIdle(int startId) { 339 synchronized (mProcessing) { 340 if (mProcessing.isEmpty() && mPending.isEmpty()) { 341 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 342 Log.v(TAG, "stopSelfIfIdle: STOP!"); 343 } 344 345 stopSelf(startId); 346 } 347 } 348 } 349 isTransientFailure(int type)350 private static boolean isTransientFailure(int type) { 351 return type > MmsSms.NO_ERROR && type < MmsSms.ERR_TYPE_GENERIC_PERMANENT; 352 } 353 getTransactionType(int msgType)354 private int getTransactionType(int msgType) { 355 switch (msgType) { 356 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 357 return Transaction.RETRIEVE_TRANSACTION; 358 case PduHeaders.MESSAGE_TYPE_READ_REC_IND: 359 return Transaction.READREC_TRANSACTION; 360 case PduHeaders.MESSAGE_TYPE_SEND_REQ: 361 return Transaction.SEND_TRANSACTION; 362 default: 363 Log.w(TAG, "Unrecognized MESSAGE_TYPE: " + msgType); 364 return -1; 365 } 366 } 367 launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork)368 private void launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork) { 369 if (noNetwork) { 370 Log.w(TAG, "launchTransaction: no network error!"); 371 onNetworkUnavailable(serviceId, txnBundle.getTransactionType()); 372 return; 373 } 374 Message msg = mServiceHandler.obtainMessage(EVENT_TRANSACTION_REQUEST); 375 msg.arg1 = serviceId; 376 msg.obj = txnBundle; 377 378 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 379 Log.v(TAG, "launchTransaction: sending message " + msg); 380 } 381 mServiceHandler.sendMessage(msg); 382 } 383 onNetworkUnavailable(int serviceId, int transactionType)384 private void onNetworkUnavailable(int serviceId, int transactionType) { 385 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 386 Log.v(TAG, "onNetworkUnavailable: sid=" + serviceId + ", type=" + transactionType); 387 } 388 389 int toastType = TOAST_NONE; 390 if (transactionType == Transaction.RETRIEVE_TRANSACTION) { 391 toastType = TOAST_DOWNLOAD_LATER; 392 } else if (transactionType == Transaction.SEND_TRANSACTION) { 393 toastType = TOAST_MSG_QUEUED; 394 } 395 if (toastType != TOAST_NONE) { 396 mToastHandler.sendEmptyMessage(toastType); 397 } 398 stopSelf(serviceId); 399 } 400 401 @Override onDestroy()402 public void onDestroy() { 403 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 404 Log.v(TAG, "Destroying TransactionService"); 405 } 406 if (!mPending.isEmpty()) { 407 Log.w(TAG, "TransactionService exiting with transaction still pending"); 408 } 409 410 releaseWakeLock(); 411 412 unregisterReceiver(mReceiver); 413 414 mServiceHandler.sendEmptyMessage(EVENT_QUIT); 415 } 416 417 @Override onBind(Intent intent)418 public IBinder onBind(Intent intent) { 419 return null; 420 } 421 422 /** 423 * Handle status change of Transaction (The Observable). 424 */ update(Observable observable)425 public void update(Observable observable) { 426 Transaction transaction = (Transaction) observable; 427 int serviceId = transaction.getServiceId(); 428 429 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 430 Log.v(TAG, "update transaction " + serviceId); 431 } 432 433 try { 434 synchronized (mProcessing) { 435 mProcessing.remove(transaction); 436 if (mPending.size() > 0) { 437 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 438 Log.v(TAG, "update: handle next pending transaction..."); 439 } 440 Message msg = mServiceHandler.obtainMessage( 441 EVENT_HANDLE_NEXT_PENDING_TRANSACTION, 442 transaction.getConnectionSettings()); 443 mServiceHandler.sendMessage(msg); 444 } 445 else if (mProcessing.isEmpty()) { 446 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 447 Log.v(TAG, "update: endMmsConnectivity"); 448 } 449 endMmsConnectivity(); 450 } else { 451 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 452 Log.v(TAG, "update: mProcessing is not empty"); 453 } 454 } 455 } 456 457 Intent intent = new Intent(TRANSACTION_COMPLETED_ACTION); 458 TransactionState state = transaction.getState(); 459 int result = state.getState(); 460 intent.putExtra(STATE, result); 461 462 switch (result) { 463 case TransactionState.SUCCESS: 464 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 465 Log.v(TAG, "Transaction complete: " + serviceId); 466 } 467 468 intent.putExtra(STATE_URI, state.getContentUri()); 469 470 // Notify user in the system-wide notification area. 471 switch (transaction.getType()) { 472 case Transaction.NOTIFICATION_TRANSACTION: 473 case Transaction.RETRIEVE_TRANSACTION: 474 // We're already in a non-UI thread called from 475 // NotificationTransacation.run(), so ok to block here. 476 long threadId = MessagingNotification.getThreadId( 477 this, state.getContentUri()); 478 MessagingNotification.blockingUpdateNewMessageIndicator(this, 479 threadId, 480 false); 481 MessagingNotification.updateDownloadFailedNotification(this); 482 break; 483 case Transaction.SEND_TRANSACTION: 484 RateController.getInstance().update(); 485 break; 486 } 487 break; 488 case TransactionState.FAILED: 489 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 490 Log.v(TAG, "Transaction failed: " + serviceId); 491 } 492 break; 493 default: 494 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 495 Log.v(TAG, "Transaction state unknown: " + 496 serviceId + " " + result); 497 } 498 break; 499 } 500 501 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 502 Log.v(TAG, "update: broadcast transaction result " + result); 503 } 504 // Broadcast the result of the transaction. 505 sendBroadcast(intent); 506 } finally { 507 transaction.detach(this); 508 stopSelfIfIdle(serviceId); 509 } 510 } 511 createWakeLock()512 private synchronized void createWakeLock() { 513 // Create a new wake lock if we haven't made one yet. 514 if (mWakeLock == null) { 515 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); 516 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MMS Connectivity"); 517 mWakeLock.setReferenceCounted(false); 518 } 519 } 520 acquireWakeLock()521 private void acquireWakeLock() { 522 // It's okay to double-acquire this because we are not using it 523 // in reference-counted mode. 524 Log.v(TAG, "mms acquireWakeLock"); 525 mWakeLock.acquire(); 526 } 527 releaseWakeLock()528 private void releaseWakeLock() { 529 // Don't release the wake lock if it hasn't been created and acquired. 530 if (mWakeLock != null && mWakeLock.isHeld()) { 531 Log.v(TAG, "mms releaseWakeLock"); 532 mWakeLock.release(); 533 } 534 } 535 beginMmsConnectivity()536 protected int beginMmsConnectivity() throws IOException { 537 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 538 Log.v(TAG, "beginMmsConnectivity"); 539 } 540 // Take a wake lock so we don't fall asleep before the message is downloaded. 541 createWakeLock(); 542 543 int result = mConnMgr.startUsingNetworkFeature( 544 ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_MMS); 545 546 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 547 Log.v(TAG, "beginMmsConnectivity: result=" + result); 548 } 549 550 switch (result) { 551 case PhoneConstants.APN_ALREADY_ACTIVE: 552 case PhoneConstants.APN_REQUEST_STARTED: 553 acquireWakeLock(); 554 return result; 555 } 556 557 throw new IOException("Cannot establish MMS connectivity"); 558 } 559 endMmsConnectivity()560 protected void endMmsConnectivity() { 561 try { 562 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 563 Log.v(TAG, "endMmsConnectivity"); 564 } 565 566 // cancel timer for renewal of lease 567 mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY); 568 if (mConnMgr != null) { 569 mConnMgr.stopUsingNetworkFeature( 570 ConnectivityManager.TYPE_MOBILE, 571 Phone.FEATURE_ENABLE_MMS); 572 } 573 } finally { 574 releaseWakeLock(); 575 } 576 } 577 578 private final class ServiceHandler extends Handler { ServiceHandler(Looper looper)579 public ServiceHandler(Looper looper) { 580 super(looper); 581 } 582 decodeMessage(Message msg)583 private String decodeMessage(Message msg) { 584 if (msg.what == EVENT_QUIT) { 585 return "EVENT_QUIT"; 586 } else if (msg.what == EVENT_CONTINUE_MMS_CONNECTIVITY) { 587 return "EVENT_CONTINUE_MMS_CONNECTIVITY"; 588 } else if (msg.what == EVENT_TRANSACTION_REQUEST) { 589 return "EVENT_TRANSACTION_REQUEST"; 590 } else if (msg.what == EVENT_HANDLE_NEXT_PENDING_TRANSACTION) { 591 return "EVENT_HANDLE_NEXT_PENDING_TRANSACTION"; 592 } else if (msg.what == EVENT_NEW_INTENT) { 593 return "EVENT_NEW_INTENT"; 594 } 595 return "unknown message.what"; 596 } 597 decodeTransactionType(int transactionType)598 private String decodeTransactionType(int transactionType) { 599 if (transactionType == Transaction.NOTIFICATION_TRANSACTION) { 600 return "NOTIFICATION_TRANSACTION"; 601 } else if (transactionType == Transaction.RETRIEVE_TRANSACTION) { 602 return "RETRIEVE_TRANSACTION"; 603 } else if (transactionType == Transaction.SEND_TRANSACTION) { 604 return "SEND_TRANSACTION"; 605 } else if (transactionType == Transaction.READREC_TRANSACTION) { 606 return "READREC_TRANSACTION"; 607 } 608 return "invalid transaction type"; 609 } 610 611 /** 612 * Handle incoming transaction requests. 613 * The incoming requests are initiated by the MMSC Server or by the 614 * MMS Client itself. 615 */ 616 @Override handleMessage(Message msg)617 public void handleMessage(Message msg) { 618 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 619 Log.v(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg)); 620 } 621 622 Transaction transaction = null; 623 624 switch (msg.what) { 625 case EVENT_NEW_INTENT: 626 onNewIntent((Intent)msg.obj, msg.arg1); 627 break; 628 629 case EVENT_QUIT: 630 getLooper().quit(); 631 return; 632 633 case EVENT_CONTINUE_MMS_CONNECTIVITY: 634 synchronized (mProcessing) { 635 if (mProcessing.isEmpty()) { 636 return; 637 } 638 } 639 640 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 641 Log.v(TAG, "handle EVENT_CONTINUE_MMS_CONNECTIVITY event..."); 642 } 643 644 try { 645 int result = beginMmsConnectivity(); 646 if (result != PhoneConstants.APN_ALREADY_ACTIVE) { 647 Log.v(TAG, "Extending MMS connectivity returned " + result + 648 " instead of APN_ALREADY_ACTIVE"); 649 // Just wait for connectivity startup without 650 // any new request of APN switch. 651 return; 652 } 653 } catch (IOException e) { 654 Log.w(TAG, "Attempt to extend use of MMS connectivity failed"); 655 return; 656 } 657 658 // Restart timer 659 renewMmsConnectivity(); 660 return; 661 662 case EVENT_TRANSACTION_REQUEST: 663 int serviceId = msg.arg1; 664 try { 665 TransactionBundle args = (TransactionBundle) msg.obj; 666 TransactionSettings transactionSettings; 667 668 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 669 Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" + 670 args.getMmscUrl() + " proxy port: " + args.getProxyAddress()); 671 } 672 673 // Set the connection settings for this transaction. 674 // If these have not been set in args, load the default settings. 675 String mmsc = args.getMmscUrl(); 676 if (mmsc != null) { 677 transactionSettings = new TransactionSettings( 678 mmsc, args.getProxyAddress(), args.getProxyPort()); 679 } else { 680 transactionSettings = new TransactionSettings( 681 TransactionService.this, null); 682 } 683 684 int transactionType = args.getTransactionType(); 685 686 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 687 Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" + 688 transactionType + " " + decodeTransactionType(transactionType)); 689 } 690 691 // Create appropriate transaction 692 switch (transactionType) { 693 case Transaction.NOTIFICATION_TRANSACTION: 694 String uri = args.getUri(); 695 if (uri != null) { 696 transaction = new NotificationTransaction( 697 TransactionService.this, serviceId, 698 transactionSettings, uri); 699 } else { 700 // Now it's only used for test purpose. 701 byte[] pushData = args.getPushData(); 702 PduParser parser = new PduParser(pushData); 703 GenericPdu ind = parser.parse(); 704 705 int type = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND; 706 if ((ind != null) && (ind.getMessageType() == type)) { 707 transaction = new NotificationTransaction( 708 TransactionService.this, serviceId, 709 transactionSettings, (NotificationInd) ind); 710 } else { 711 Log.e(TAG, "Invalid PUSH data."); 712 transaction = null; 713 return; 714 } 715 } 716 break; 717 case Transaction.RETRIEVE_TRANSACTION: 718 transaction = new RetrieveTransaction( 719 TransactionService.this, serviceId, 720 transactionSettings, args.getUri()); 721 break; 722 case Transaction.SEND_TRANSACTION: 723 transaction = new SendTransaction( 724 TransactionService.this, serviceId, 725 transactionSettings, args.getUri()); 726 break; 727 case Transaction.READREC_TRANSACTION: 728 transaction = new ReadRecTransaction( 729 TransactionService.this, serviceId, 730 transactionSettings, args.getUri()); 731 break; 732 default: 733 Log.w(TAG, "Invalid transaction type: " + serviceId); 734 transaction = null; 735 return; 736 } 737 738 if (!processTransaction(transaction)) { 739 transaction = null; 740 return; 741 } 742 743 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 744 Log.v(TAG, "Started processing of incoming message: " + msg); 745 } 746 } catch (Exception ex) { 747 Log.w(TAG, "Exception occurred while handling message: " + msg, ex); 748 749 if (transaction != null) { 750 try { 751 transaction.detach(TransactionService.this); 752 if (mProcessing.contains(transaction)) { 753 synchronized (mProcessing) { 754 mProcessing.remove(transaction); 755 } 756 } 757 } catch (Throwable t) { 758 Log.e(TAG, "Unexpected Throwable.", t); 759 } finally { 760 // Set transaction to null to allow stopping the 761 // transaction service. 762 transaction = null; 763 } 764 } 765 } finally { 766 if (transaction == null) { 767 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 768 Log.v(TAG, "Transaction was null. Stopping self: " + serviceId); 769 } 770 endMmsConnectivity(); 771 stopSelf(serviceId); 772 } 773 } 774 return; 775 case EVENT_HANDLE_NEXT_PENDING_TRANSACTION: 776 processPendingTransaction(transaction, (TransactionSettings) msg.obj); 777 return; 778 default: 779 Log.w(TAG, "what=" + msg.what); 780 return; 781 } 782 } 783 markAllPendingTransactionsAsFailed()784 public void markAllPendingTransactionsAsFailed() { 785 synchronized (mProcessing) { 786 while (mPending.size() != 0) { 787 Transaction transaction = mPending.remove(0); 788 transaction.mTransactionState.setState(TransactionState.FAILED); 789 if (transaction instanceof SendTransaction) { 790 Uri uri = ((SendTransaction)transaction).mSendReqURI; 791 transaction.mTransactionState.setContentUri(uri); 792 int respStatus = PduHeaders.RESPONSE_STATUS_ERROR_NETWORK_PROBLEM; 793 ContentValues values = new ContentValues(1); 794 values.put(Mms.RESPONSE_STATUS, respStatus); 795 796 SqliteWrapper.update(TransactionService.this, 797 TransactionService.this.getContentResolver(), 798 uri, values, null, null); 799 } 800 transaction.notifyObservers(); 801 } 802 } 803 } 804 processPendingTransaction(Transaction transaction, TransactionSettings settings)805 public void processPendingTransaction(Transaction transaction, 806 TransactionSettings settings) { 807 808 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 809 Log.v(TAG, "processPendingTxn: transaction=" + transaction); 810 } 811 812 int numProcessTransaction = 0; 813 synchronized (mProcessing) { 814 if (mPending.size() != 0) { 815 transaction = mPending.remove(0); 816 } 817 numProcessTransaction = mProcessing.size(); 818 } 819 820 if (transaction != null) { 821 if (settings != null) { 822 transaction.setConnectionSettings(settings); 823 } 824 825 /* 826 * Process deferred transaction 827 */ 828 try { 829 int serviceId = transaction.getServiceId(); 830 831 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 832 Log.v(TAG, "processPendingTxn: process " + serviceId); 833 } 834 835 if (processTransaction(transaction)) { 836 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 837 Log.v(TAG, "Started deferred processing of transaction " 838 + transaction); 839 } 840 } else { 841 transaction = null; 842 stopSelf(serviceId); 843 } 844 } catch (IOException e) { 845 Log.w(TAG, e.getMessage(), e); 846 } 847 } else { 848 if (numProcessTransaction == 0) { 849 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 850 Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity"); 851 } 852 endMmsConnectivity(); 853 } 854 } 855 } 856 857 /** 858 * Internal method to begin processing a transaction. 859 * @param transaction the transaction. Must not be {@code null}. 860 * @return {@code true} if process has begun or will begin. {@code false} 861 * if the transaction should be discarded. 862 * @throws IOException if connectivity for MMS traffic could not be 863 * established. 864 */ processTransaction(Transaction transaction)865 private boolean processTransaction(Transaction transaction) throws IOException { 866 // Check if transaction already processing 867 synchronized (mProcessing) { 868 for (Transaction t : mPending) { 869 if (t.isEquivalent(transaction)) { 870 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 871 Log.v(TAG, "Transaction already pending: " + 872 transaction.getServiceId()); 873 } 874 return true; 875 } 876 } 877 for (Transaction t : mProcessing) { 878 if (t.isEquivalent(transaction)) { 879 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 880 Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId()); 881 } 882 return true; 883 } 884 } 885 886 /* 887 * Make sure that the network connectivity necessary 888 * for MMS traffic is enabled. If it is not, we need 889 * to defer processing the transaction until 890 * connectivity is established. 891 */ 892 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 893 Log.v(TAG, "processTransaction: call beginMmsConnectivity..."); 894 } 895 int connectivityResult = beginMmsConnectivity(); 896 if (connectivityResult == PhoneConstants.APN_REQUEST_STARTED) { 897 mPending.add(transaction); 898 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 899 Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " + 900 "defer transaction pending MMS connectivity"); 901 } 902 return true; 903 } 904 // If there is already a transaction in processing list, because of the previous 905 // beginMmsConnectivity call and there is another transaction just at a time, 906 // when the pdp is connected, there will be a case of adding the new transaction 907 // to the Processing list. But Processing list is never traversed to 908 // resend, resulting in transaction not completed/sent. 909 if (mProcessing.size() > 0) { 910 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 911 Log.v(TAG, "Adding transaction to 'mPending' list: " + transaction); 912 } 913 mPending.add(transaction); 914 return true; 915 } else { 916 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 917 Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction); 918 } 919 mProcessing.add(transaction); 920 } 921 } 922 923 // Set a timer to keep renewing our "lease" on the MMS connection 924 sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 925 APN_EXTENSION_WAIT); 926 927 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 928 Log.v(TAG, "processTransaction: starting transaction " + transaction); 929 } 930 931 // Attach to transaction and process it 932 transaction.attach(TransactionService.this); 933 transaction.process(); 934 return true; 935 } 936 } 937 renewMmsConnectivity()938 private void renewMmsConnectivity() { 939 // Set a timer to keep renewing our "lease" on the MMS connection 940 mServiceHandler.sendMessageDelayed( 941 mServiceHandler.obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), 942 APN_EXTENSION_WAIT); 943 } 944 945 private class ConnectivityBroadcastReceiver extends BroadcastReceiver { 946 @Override onReceive(Context context, Intent intent)947 public void onReceive(Context context, Intent intent) { 948 String action = intent.getAction(); 949 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 950 Log.w(TAG, "ConnectivityBroadcastReceiver.onReceive() action: " + action); 951 } 952 953 if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { 954 return; 955 } 956 957 NetworkInfo mmsNetworkInfo = null; 958 959 if (mConnMgr != null && mConnMgr.getMobileDataEnabled()) { 960 mmsNetworkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); 961 } else { 962 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 963 Log.v(TAG, "mConnMgr is null, bail"); 964 } 965 } 966 967 /* 968 * If we are being informed that connectivity has been established 969 * to allow MMS traffic, then proceed with processing the pending 970 * transaction, if any. 971 */ 972 973 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 974 Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + mmsNetworkInfo); 975 } 976 977 // Check availability of the mobile network. 978 if (mmsNetworkInfo == null) { 979 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 980 Log.v(TAG, "mms type is null or mobile data is turned off, bail"); 981 } 982 } else { 983 // This is a very specific fix to handle the case where the phone receives an 984 // incoming call during the time we're trying to setup the mms connection. 985 // When the call ends, restart the process of mms connectivity. 986 if (Phone.REASON_VOICE_CALL_ENDED.equals(mmsNetworkInfo.getReason())) { 987 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 988 Log.v(TAG, " reason is " + Phone.REASON_VOICE_CALL_ENDED + 989 ", retrying mms connectivity"); 990 } 991 renewMmsConnectivity(); 992 return; 993 } 994 995 if (mmsNetworkInfo.isConnected()) { 996 TransactionSettings settings = new TransactionSettings( 997 TransactionService.this, mmsNetworkInfo.getExtraInfo()); 998 // If this APN doesn't have an MMSC, mark everything as failed and bail. 999 if (TextUtils.isEmpty(settings.getMmscUrl())) { 1000 Log.v(TAG, " empty MMSC url, bail"); 1001 mToastHandler.sendEmptyMessage(TOAST_NO_APN); 1002 mServiceHandler.markAllPendingTransactionsAsFailed(); 1003 endMmsConnectivity(); 1004 return; 1005 } 1006 mServiceHandler.processPendingTransaction(null, settings); 1007 } else { 1008 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 1009 Log.v(TAG, " TYPE_MOBILE_MMS not connected, bail"); 1010 } 1011 1012 // Retry mms connectivity once it's possible to connect 1013 if (mmsNetworkInfo.isAvailable()) { 1014 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 1015 Log.v(TAG, " retrying mms connectivity for it's available"); 1016 } 1017 renewMmsConnectivity(); 1018 } 1019 } 1020 } 1021 } 1022 }; 1023 } 1024