SMSDispatcher.java revision 8503144665894f1b5008f540e0cf36c948c763ef
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.telephony; 18 19import android.app.Activity; 20import android.app.PendingIntent; 21import android.app.AlertDialog; 22import android.app.PendingIntent.CanceledException; 23import android.content.BroadcastReceiver; 24import android.content.ContentResolver; 25import android.content.ContentValues; 26import android.content.Context; 27import android.content.Intent; 28import android.content.DialogInterface; 29import android.content.IntentFilter; 30import android.content.res.Resources; 31import android.database.Cursor; 32import android.database.SQLException; 33import android.net.Uri; 34import android.os.AsyncResult; 35import android.os.Environment; 36import android.os.Handler; 37import android.os.Message; 38import android.os.PowerManager; 39import android.os.StatFs; 40import android.provider.Telephony; 41import android.provider.Telephony.Sms.Intents; 42import android.provider.Settings; 43import android.telephony.SmsMessage; 44import android.telephony.ServiceState; 45import android.util.Config; 46import android.util.Log; 47import android.view.WindowManager; 48 49import com.android.internal.util.HexDump; 50 51import java.io.ByteArrayOutputStream; 52import java.util.ArrayList; 53import java.util.HashMap; 54import java.util.Random; 55 56import com.android.internal.R; 57 58import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE; 59import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE; 60import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU; 61import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF; 62import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED; 63import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE; 64 65 66public abstract class SMSDispatcher extends Handler { 67 private static final String TAG = "SMS"; 68 69 /** Default checking period for SMS sent without user permit */ 70 private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000; 71 72 /** Default number of SMS sent in checking period without user permit */ 73 private static final int DEFAULT_SMS_MAX_COUNT = 100; 74 75 /** Default timeout for SMS sent query */ 76 private static final int DEFAULT_SMS_TIMEOUT = 6000; 77 78 protected static final String[] RAW_PROJECTION = new String[] { 79 "pdu", 80 "sequence", 81 "destination_port", 82 }; 83 84 static final protected int EVENT_NEW_SMS = 1; 85 86 static final protected int EVENT_SEND_SMS_COMPLETE = 2; 87 88 /** Retry sending a previously failed SMS message */ 89 static final protected int EVENT_SEND_RETRY = 3; 90 91 /** Status report received */ 92 static final protected int EVENT_NEW_SMS_STATUS_REPORT = 5; 93 94 /** SIM/RUIM storage is full */ 95 static final protected int EVENT_ICC_FULL = 6; 96 97 /** SMS confirm required */ 98 static final protected int EVENT_POST_ALERT = 7; 99 100 /** Send the user confirmed SMS */ 101 static final protected int EVENT_SEND_CONFIRMED_SMS = 8; 102 103 /** Alert is timeout */ 104 static final protected int EVENT_ALERT_TIMEOUT = 9; 105 106 /** Stop the sending */ 107 static final protected int EVENT_STOP_SENDING = 10; 108 109 /** Memory status reporting is acknowledged by RIL */ 110 static final protected int EVENT_REPORT_MEMORY_STATUS_DONE = 11; 111 112 /** Radio is ON */ 113 static final protected int EVENT_RADIO_ON = 12; 114 115 /** New broadcast SMS */ 116 static final protected int EVENT_NEW_BROADCAST_SMS = 13; 117 118 protected Phone mPhone; 119 protected Context mContext; 120 protected ContentResolver mResolver; 121 protected CommandsInterface mCm; 122 123 protected final WapPushOverSms mWapPush; 124 125 protected final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); 126 127 /** Maximum number of times to retry sending a failed SMS. */ 128 private static final int MAX_SEND_RETRIES = 3; 129 /** Delay before next send attempt on a failed SMS, in milliseconds. */ 130 private static final int SEND_RETRY_DELAY = 2000; 131 /** single part SMS */ 132 private static final int SINGLE_PART_SMS = 1; 133 /** Message sending queue limit */ 134 private static final int MO_MSG_QUEUE_LIMIT = 5; 135 136 /** 137 * Message reference for a CONCATENATED_8_BIT_REFERENCE or 138 * CONCATENATED_16_BIT_REFERENCE message set. Should be 139 * incremented for each set of concatenated messages. 140 */ 141 private static int sConcatenatedRef; 142 143 private SmsCounter mCounter; 144 145 private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT); 146 147 /** Wake lock to ensure device stays awake while dispatching the SMS intent. */ 148 private PowerManager.WakeLock mWakeLock; 149 150 /** 151 * Hold the wake lock for 5 seconds, which should be enough time for 152 * any receiver(s) to grab its own wake lock. 153 */ 154 private final int WAKE_LOCK_TIMEOUT = 5000; 155 156 protected boolean mStorageAvailable = true; 157 protected boolean mReportMemoryStatusPending = false; 158 159 protected static int getNextConcatenatedRef() { 160 sConcatenatedRef += 1; 161 return sConcatenatedRef; 162 } 163 164 /** 165 * Implement the per-application based SMS control, which only allows 166 * a limit on the number of SMS/MMS messages an app can send in checking 167 * period. 168 */ 169 private class SmsCounter { 170 private int mCheckPeriod; 171 private int mMaxAllowed; 172 private HashMap<String, ArrayList<Long>> mSmsStamp; 173 174 /** 175 * Create SmsCounter 176 * @param mMax is the number of SMS allowed without user permit 177 * @param mPeriod is the checking period 178 */ 179 SmsCounter(int mMax, int mPeriod) { 180 mMaxAllowed = mMax; 181 mCheckPeriod = mPeriod; 182 mSmsStamp = new HashMap<String, ArrayList<Long>> (); 183 } 184 185 /** 186 * Check to see if an application allow to send new SMS messages 187 * 188 * @param appName is the application sending sms 189 * @param smsWaiting is the number of new sms wants to be sent 190 * @return true if application is allowed to send the requested number 191 * of new sms messages 192 */ 193 boolean check(String appName, int smsWaiting) { 194 if (!mSmsStamp.containsKey(appName)) { 195 mSmsStamp.put(appName, new ArrayList<Long>()); 196 } 197 198 return isUnderLimit(mSmsStamp.get(appName), smsWaiting); 199 } 200 201 private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) { 202 Long ct = System.currentTimeMillis(); 203 204 Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct); 205 206 while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) { 207 sent.remove(0); 208 } 209 210 211 if ( (sent.size() + smsWaiting) <= mMaxAllowed) { 212 for (int i = 0; i < smsWaiting; i++ ) { 213 sent.add(ct); 214 } 215 return true; 216 } 217 return false; 218 } 219 } 220 221 protected SMSDispatcher(PhoneBase phone) { 222 mPhone = phone; 223 mWapPush = new WapPushOverSms(phone, this); 224 mContext = phone.getContext(); 225 mResolver = mContext.getContentResolver(); 226 mCm = phone.mCM; 227 228 createWakelock(); 229 230 int check_period = Settings.Secure.getInt(mResolver, 231 Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS, 232 DEFAULT_SMS_CHECK_PERIOD); 233 int max_count = Settings.Secure.getInt(mResolver, 234 Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT, 235 DEFAULT_SMS_MAX_COUNT); 236 mCounter = new SmsCounter(max_count, check_period); 237 238 mCm.setOnNewSMS(this, EVENT_NEW_SMS, null); 239 mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null); 240 mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null); 241 mCm.registerForOn(this, EVENT_RADIO_ON, null); 242 243 // Don't always start message ref at 0. 244 sConcatenatedRef = new Random().nextInt(256); 245 246 // Register for device storage intents. Use these to notify the RIL 247 // that storage for SMS is or is not available. 248 IntentFilter filter = new IntentFilter(); 249 filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL); 250 filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL); 251 mContext.registerReceiver(mResultReceiver, filter); 252 } 253 254 public void dispose() { 255 mCm.unSetOnNewSMS(this); 256 mCm.unSetOnSmsStatus(this); 257 mCm.unSetOnIccSmsFull(this); 258 mCm.unregisterForOn(this); 259 } 260 261 @Override 262 protected void finalize() { 263 Log.d(TAG, "SMSDispatcher finalized"); 264 } 265 266 267 /* TODO: Need to figure out how to keep track of status report routing in a 268 * persistent manner. If the phone process restarts (reboot or crash), 269 * we will lose this list and any status reports that come in after 270 * will be dropped. 271 */ 272 /** Sent messages awaiting a delivery status report. */ 273 protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>(); 274 275 /** 276 * Handles events coming from the phone stack. Overridden from handler. 277 * 278 * @param msg the message to handle 279 */ 280 @Override 281 public void handleMessage(Message msg) { 282 AsyncResult ar; 283 284 switch (msg.what) { 285 case EVENT_NEW_SMS: 286 // A new SMS has been received by the device 287 if (Config.LOGD) { 288 Log.d(TAG, "New SMS Message Received"); 289 } 290 291 SmsMessage sms; 292 293 ar = (AsyncResult) msg.obj; 294 295 if (ar.exception != null) { 296 Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception); 297 return; 298 } 299 300 sms = (SmsMessage) ar.result; 301 try { 302 int result = dispatchMessage(sms.mWrappedSmsMessage); 303 if (result != Activity.RESULT_OK) { 304 // RESULT_OK means that message was broadcast for app(s) to handle. 305 // Any other result, we should ack here. 306 boolean handled = (result == Intents.RESULT_SMS_HANDLED); 307 notifyAndAcknowledgeLastIncomingSms(handled, result, null); 308 } 309 } catch (RuntimeException ex) { 310 Log.e(TAG, "Exception dispatching message", ex); 311 notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null); 312 } 313 314 break; 315 316 case EVENT_SEND_SMS_COMPLETE: 317 // An outbound SMS has been successfully transferred, or failed. 318 handleSendComplete((AsyncResult) msg.obj); 319 break; 320 321 case EVENT_SEND_RETRY: 322 sendSms((SmsTracker) msg.obj); 323 break; 324 325 case EVENT_NEW_SMS_STATUS_REPORT: 326 handleStatusReport((AsyncResult)msg.obj); 327 break; 328 329 case EVENT_ICC_FULL: 330 handleIccFull(); 331 break; 332 333 case EVENT_POST_ALERT: 334 handleReachSentLimit((SmsTracker)(msg.obj)); 335 break; 336 337 case EVENT_ALERT_TIMEOUT: 338 ((AlertDialog)(msg.obj)).dismiss(); 339 msg.obj = null; 340 if (mSTrackers.isEmpty() == false) { 341 try { 342 SmsTracker sTracker = mSTrackers.remove(0); 343 sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); 344 } catch (CanceledException ex) { 345 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED"); 346 } 347 } 348 if (Config.LOGD) { 349 Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending"); 350 } 351 break; 352 353 case EVENT_SEND_CONFIRMED_SMS: 354 if (mSTrackers.isEmpty() == false) { 355 SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1); 356 if (isMultipartTracker(sTracker)) { 357 sendMultipartSms(sTracker); 358 } else { 359 sendSms(sTracker); 360 } 361 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj); 362 } 363 break; 364 365 case EVENT_STOP_SENDING: 366 if (mSTrackers.isEmpty() == false) { 367 // Remove the latest one. 368 try { 369 SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1); 370 sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); 371 } catch (CanceledException ex) { 372 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED"); 373 } 374 removeMessages(EVENT_ALERT_TIMEOUT, msg.obj); 375 } 376 break; 377 378 case EVENT_REPORT_MEMORY_STATUS_DONE: 379 ar = (AsyncResult)msg.obj; 380 if (ar.exception != null) { 381 mReportMemoryStatusPending = true; 382 Log.v(TAG, "Memory status report to modem pending : mStorageAvailable = " 383 + mStorageAvailable); 384 } else { 385 mReportMemoryStatusPending = false; 386 } 387 break; 388 389 case EVENT_RADIO_ON: 390 if (mReportMemoryStatusPending) { 391 Log.v(TAG, "Sending pending memory status report : mStorageAvailable = " 392 + mStorageAvailable); 393 mCm.reportSmsMemoryStatus(mStorageAvailable, 394 obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); 395 } 396 397 case EVENT_NEW_BROADCAST_SMS: 398 handleBroadcastSms((AsyncResult)msg.obj); 399 break; 400 } 401 } 402 403 private void createWakelock() { 404 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 405 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher"); 406 mWakeLock.setReferenceCounted(true); 407 } 408 409 /** 410 * Grabs a wake lock and sends intent as an ordered broadcast. 411 * The resultReceiver will check for errors and ACK/NACK back 412 * to the RIL. 413 * 414 * @param intent intent to broadcast 415 * @param permission Receivers are required to have this permission 416 */ 417 void dispatch(Intent intent, String permission) { 418 // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any 419 // receivers time to take their own wake locks. 420 mWakeLock.acquire(WAKE_LOCK_TIMEOUT); 421 mContext.sendOrderedBroadcast(intent, permission, mResultReceiver, 422 this, Activity.RESULT_OK, null, null); 423 } 424 425 /** 426 * Called when SIM_FULL message is received from the RIL. Notifies interested 427 * parties that SIM storage for SMS messages is full. 428 */ 429 private void handleIccFull(){ 430 // broadcast SIM_FULL intent 431 Intent intent = new Intent(Intents.SIM_FULL_ACTION); 432 mWakeLock.acquire(WAKE_LOCK_TIMEOUT); 433 mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS"); 434 } 435 436 /** 437 * Called when a status report is received. This should correspond to 438 * a previously successful SEND. 439 * 440 * @param ar AsyncResult passed into the message handler. ar.result should 441 * be a String representing the status report PDU, as ASCII hex. 442 */ 443 protected abstract void handleStatusReport(AsyncResult ar); 444 445 /** 446 * Called when SMS send completes. Broadcasts a sentIntent on success. 447 * On failure, either sets up retries or broadcasts a sentIntent with 448 * the failure in the result code. 449 * 450 * @param ar AsyncResult passed into the message handler. ar.result should 451 * an SmsResponse instance if send was successful. ar.userObj 452 * should be an SmsTracker instance. 453 */ 454 protected void handleSendComplete(AsyncResult ar) { 455 SmsTracker tracker = (SmsTracker) ar.userObj; 456 PendingIntent sentIntent = tracker.mSentIntent; 457 458 if (ar.exception == null) { 459 if (Config.LOGD) { 460 Log.d(TAG, "SMS send complete. Broadcasting " 461 + "intent: " + sentIntent); 462 } 463 464 if (tracker.mDeliveryIntent != null) { 465 // Expecting a status report. Add it to the list. 466 int messageRef = ((SmsResponse)ar.result).messageRef; 467 tracker.mMessageRef = messageRef; 468 deliveryPendingList.add(tracker); 469 } 470 471 if (sentIntent != null) { 472 try { 473 sentIntent.send(Activity.RESULT_OK); 474 } catch (CanceledException ex) {} 475 } 476 } else { 477 if (Config.LOGD) { 478 Log.d(TAG, "SMS send failed"); 479 } 480 481 int ss = mPhone.getServiceState().getState(); 482 483 if (ss != ServiceState.STATE_IN_SERVICE) { 484 handleNotInService(ss, tracker); 485 } else if ((((CommandException)(ar.exception)).getCommandError() 486 == CommandException.Error.SMS_FAIL_RETRY) && 487 tracker.mRetryCount < MAX_SEND_RETRIES) { 488 // Retry after a delay if needed. 489 // TODO: According to TS 23.040, 9.2.3.6, we should resend 490 // with the same TP-MR as the failed message, and 491 // TP-RD set to 1. However, we don't have a means of 492 // knowing the MR for the failed message (EF_SMSstatus 493 // may or may not have the MR corresponding to this 494 // message, depending on the failure). Also, in some 495 // implementations this retry is handled by the baseband. 496 tracker.mRetryCount++; 497 Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker); 498 sendMessageDelayed(retryMsg, SEND_RETRY_DELAY); 499 } else if (tracker.mSentIntent != null) { 500 int error = RESULT_ERROR_GENERIC_FAILURE; 501 502 if (((CommandException)(ar.exception)).getCommandError() 503 == CommandException.Error.FDN_CHECK_FAILURE) { 504 error = RESULT_ERROR_FDN_CHECK_FAILURE; 505 } 506 // Done retrying; return an error to the app. 507 try { 508 Intent fillIn = new Intent(); 509 if (ar.result != null) { 510 fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode); 511 } 512 tracker.mSentIntent.send(mContext, error, fillIn); 513 514 } catch (CanceledException ex) {} 515 } 516 } 517 } 518 519 /** 520 * Handles outbound message when the phone is not in service. 521 * 522 * @param ss Current service state. Valid values are: 523 * OUT_OF_SERVICE 524 * EMERGENCY_ONLY 525 * POWER_OFF 526 * @param tracker An SmsTracker for the current message. 527 */ 528 protected void handleNotInService(int ss, SmsTracker tracker) { 529 if (tracker.mSentIntent != null) { 530 try { 531 if (ss == ServiceState.STATE_POWER_OFF) { 532 tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF); 533 } else { 534 tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE); 535 } 536 } catch (CanceledException ex) {} 537 } 538 } 539 540 /** 541 * Dispatches an incoming SMS messages. 542 * 543 * @param sms the incoming message from the phone 544 * @return a result code from {@link Telephony.Sms.Intents}, or 545 * {@link Activity#RESULT_OK} if the message has been broadcast 546 * to applications 547 */ 548 protected abstract int dispatchMessage(SmsMessageBase sms); 549 550 551 /** 552 * If this is the last part send the parts out to the application, otherwise 553 * the part is stored for later processing. 554 * 555 * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null. 556 * @return a result code from {@link Telephony.Sms.Intents}, or 557 * {@link Activity#RESULT_OK} if the message has been broadcast 558 * to applications 559 */ 560 protected int processMessagePart(SmsMessageBase sms, 561 SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) { 562 563 // Lookup all other related parts 564 StringBuilder where = new StringBuilder("reference_number ="); 565 where.append(concatRef.refNumber); 566 where.append(" AND address = ?"); 567 String[] whereArgs = new String[] {sms.getOriginatingAddress()}; 568 569 byte[][] pdus = null; 570 Cursor cursor = null; 571 try { 572 cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null); 573 int cursorCount = cursor.getCount(); 574 if (cursorCount != concatRef.msgCount - 1) { 575 // We don't have all the parts yet, store this one away 576 ContentValues values = new ContentValues(); 577 values.put("date", new Long(sms.getTimestampMillis())); 578 values.put("pdu", HexDump.toHexString(sms.getPdu())); 579 values.put("address", sms.getOriginatingAddress()); 580 values.put("reference_number", concatRef.refNumber); 581 values.put("count", concatRef.msgCount); 582 values.put("sequence", concatRef.seqNumber); 583 if (portAddrs != null) { 584 values.put("destination_port", portAddrs.destPort); 585 } 586 mResolver.insert(mRawUri, values); 587 return Intents.RESULT_SMS_HANDLED; 588 } 589 590 // All the parts are in place, deal with them 591 int pduColumn = cursor.getColumnIndex("pdu"); 592 int sequenceColumn = cursor.getColumnIndex("sequence"); 593 594 pdus = new byte[concatRef.msgCount][]; 595 for (int i = 0; i < cursorCount; i++) { 596 cursor.moveToNext(); 597 int cursorSequence = (int)cursor.getLong(sequenceColumn); 598 pdus[cursorSequence - 1] = HexDump.hexStringToByteArray( 599 cursor.getString(pduColumn)); 600 } 601 // This one isn't in the DB, so add it 602 pdus[concatRef.seqNumber - 1] = sms.getPdu(); 603 604 // Remove the parts from the database 605 mResolver.delete(mRawUri, where.toString(), whereArgs); 606 } catch (SQLException e) { 607 Log.e(TAG, "Can't access multipart SMS database", e); 608 // TODO: Would OUT_OF_MEMORY be more appropriate? 609 return Intents.RESULT_SMS_GENERIC_ERROR; 610 } finally { 611 if (cursor != null) cursor.close(); 612 } 613 614 /** 615 * TODO(cleanup): The following code has duplicated logic with 616 * the radio-specific dispatchMessage code, which is fragile, 617 * in addition to being redundant. Instead, if this method 618 * maybe returned the reassembled message (or just contents), 619 * the following code (which is not really related to 620 * reconstruction) could be better consolidated. 621 */ 622 623 // Dispatch the PDUs to applications 624 if (portAddrs != null) { 625 if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { 626 // Build up the data stream 627 ByteArrayOutputStream output = new ByteArrayOutputStream(); 628 for (int i = 0; i < concatRef.msgCount; i++) { 629 SmsMessage msg = SmsMessage.createFromPdu(pdus[i]); 630 byte[] data = msg.getUserData(); 631 output.write(data, 0, data.length); 632 } 633 // Handle the PUSH 634 return mWapPush.dispatchWapPdu(output.toByteArray()); 635 } else { 636 // The messages were sent to a port, so concoct a URI for it 637 dispatchPortAddressedPdus(pdus, portAddrs.destPort); 638 } 639 } else { 640 // The messages were not sent to a port 641 dispatchPdus(pdus); 642 } 643 return Activity.RESULT_OK; 644 } 645 646 /** 647 * Dispatches standard PDUs to interested applications 648 * 649 * @param pdus The raw PDUs making up the message 650 */ 651 protected void dispatchPdus(byte[][] pdus) { 652 Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION); 653 intent.putExtra("pdus", pdus); 654 dispatch(intent, "android.permission.RECEIVE_SMS"); 655 } 656 657 /** 658 * Dispatches port addressed PDUs to interested applications 659 * 660 * @param pdus The raw PDUs making up the message 661 * @param port The destination port of the messages 662 */ 663 protected void dispatchPortAddressedPdus(byte[][] pdus, int port) { 664 Uri uri = Uri.parse("sms://localhost:" + port); 665 Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri); 666 intent.putExtra("pdus", pdus); 667 dispatch(intent, "android.permission.RECEIVE_SMS"); 668 } 669 670 /** 671 * Send a data based SMS to a specific application port. 672 * 673 * @param destAddr the address to send the message to 674 * @param scAddr is the service center address or null to use 675 * the current default SMSC 676 * @param destPort the port to deliver the message to 677 * @param data the body of the message to send 678 * @param sentIntent if not NULL this <code>PendingIntent</code> is 679 * broadcast when the message is successfully sent, or failed. 680 * The result code will be <code>Activity.RESULT_OK<code> for success, 681 * or one of these errors:<br> 682 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> 683 * <code>RESULT_ERROR_RADIO_OFF</code><br> 684 * <code>RESULT_ERROR_NULL_PDU</code><br> 685 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include 686 * the extra "errorCode" containing a radio technology specific value, 687 * generally only useful for troubleshooting.<br> 688 * The per-application based SMS control checks sentIntent. If sentIntent 689 * is NULL the caller will be checked against all unknown applications, 690 * which cause smaller number of SMS to be sent in checking period. 691 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is 692 * broadcast when the message is delivered to the recipient. The 693 * raw pdu of the status report is in the extended data ("pdu"). 694 */ 695 protected abstract void sendData(String destAddr, String scAddr, int destPort, 696 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent); 697 698 /** 699 * Send a text based SMS. 700 * 701 * @param destAddr the address to send the message to 702 * @param scAddr is the service center address or null to use 703 * the current default SMSC 704 * @param text the body of the message to send 705 * @param sentIntent if not NULL this <code>PendingIntent</code> is 706 * broadcast when the message is successfully sent, or failed. 707 * The result code will be <code>Activity.RESULT_OK<code> for success, 708 * or one of these errors:<br> 709 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> 710 * <code>RESULT_ERROR_RADIO_OFF</code><br> 711 * <code>RESULT_ERROR_NULL_PDU</code><br> 712 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include 713 * the extra "errorCode" containing a radio technology specific value, 714 * generally only useful for troubleshooting.<br> 715 * The per-application based SMS control checks sentIntent. If sentIntent 716 * is NULL the caller will be checked against all unknown applications, 717 * which cause smaller number of SMS to be sent in checking period. 718 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is 719 * broadcast when the message is delivered to the recipient. The 720 * raw pdu of the status report is in the extended data ("pdu"). 721 */ 722 protected abstract void sendText(String destAddr, String scAddr, 723 String text, PendingIntent sentIntent, PendingIntent deliveryIntent); 724 725 /** 726 * Send a multi-part text based SMS. 727 * 728 * @param destAddr the address to send the message to 729 * @param scAddr is the service center address or null to use 730 * the current default SMSC 731 * @param parts an <code>ArrayList</code> of strings that, in order, 732 * comprise the original message 733 * @param sentIntents if not null, an <code>ArrayList</code> of 734 * <code>PendingIntent</code>s (one for each message part) that is 735 * broadcast when the corresponding message part has been sent. 736 * The result code will be <code>Activity.RESULT_OK<code> for success, 737 * or one of these errors: 738 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 739 * <code>RESULT_ERROR_RADIO_OFF</code> 740 * <code>RESULT_ERROR_NULL_PDU</code>. 741 * The per-application based SMS control checks sentIntent. If sentIntent 742 * is NULL the caller will be checked against all unknown applications, 743 * which cause smaller number of SMS to be sent in checking period. 744 * @param deliveryIntents if not null, an <code>ArrayList</code> of 745 * <code>PendingIntent</code>s (one for each message part) that is 746 * broadcast when the corresponding message part has been delivered 747 * to the recipient. The raw pdu of the status report is in the 748 * extended data ("pdu"). 749 */ 750 protected abstract void sendMultipartText(String destAddr, String scAddr, 751 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 752 ArrayList<PendingIntent> deliveryIntents); 753 754 /** 755 * Send a SMS 756 * 757 * @param smsc the SMSC to send the message through, or NULL for the 758 * default SMSC 759 * @param pdu the raw PDU to send 760 * @param sentIntent if not NULL this <code>Intent</code> is 761 * broadcast when the message is successfully sent, or failed. 762 * The result code will be <code>Activity.RESULT_OK<code> for success, 763 * or one of these errors: 764 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 765 * <code>RESULT_ERROR_RADIO_OFF</code> 766 * <code>RESULT_ERROR_NULL_PDU</code>. 767 * The per-application based SMS control checks sentIntent. If sentIntent 768 * is NULL the caller will be checked against all unknown applications, 769 * which cause smaller number of SMS to be sent in checking period. 770 * @param deliveryIntent if not NULL this <code>Intent</code> is 771 * broadcast when the message is delivered to the recipient. The 772 * raw pdu of the status report is in the extended data ("pdu"). 773 */ 774 protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, 775 PendingIntent deliveryIntent) { 776 if (pdu == null) { 777 if (sentIntent != null) { 778 try { 779 sentIntent.send(RESULT_ERROR_NULL_PDU); 780 } catch (CanceledException ex) {} 781 } 782 return; 783 } 784 785 HashMap<String, Object> map = new HashMap<String, Object>(); 786 map.put("smsc", smsc); 787 map.put("pdu", pdu); 788 789 SmsTracker tracker = new SmsTracker(map, sentIntent, 790 deliveryIntent); 791 int ss = mPhone.getServiceState().getState(); 792 793 if (ss != ServiceState.STATE_IN_SERVICE) { 794 handleNotInService(ss, tracker); 795 } else { 796 String appName = getAppNameByIntent(sentIntent); 797 if (mCounter.check(appName, SINGLE_PART_SMS)) { 798 sendSms(tracker); 799 } else { 800 sendMessage(obtainMessage(EVENT_POST_ALERT, tracker)); 801 } 802 } 803 } 804 805 /** 806 * Post an alert while SMS needs user confirm. 807 * 808 * An SmsTracker for the current message. 809 */ 810 protected void handleReachSentLimit(SmsTracker tracker) { 811 if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) { 812 // Deny the sending when the queue limit is reached. 813 try { 814 tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); 815 } catch (CanceledException ex) { 816 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED"); 817 } 818 return; 819 } 820 821 Resources r = Resources.getSystem(); 822 823 String appName = getAppNameByIntent(tracker.mSentIntent); 824 825 AlertDialog d = new AlertDialog.Builder(mContext) 826 .setTitle(r.getString(R.string.sms_control_title)) 827 .setMessage(appName + " " + r.getString(R.string.sms_control_message)) 828 .setPositiveButton(r.getString(R.string.sms_control_yes), mListener) 829 .setNegativeButton(r.getString(R.string.sms_control_no), mListener) 830 .create(); 831 832 d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 833 d.show(); 834 835 mSTrackers.add(tracker); 836 sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d), 837 DEFAULT_SMS_TIMEOUT); 838 } 839 840 protected String getAppNameByIntent(PendingIntent intent) { 841 Resources r = Resources.getSystem(); 842 return (intent != null) ? intent.getTargetPackage() 843 : r.getString(R.string.sms_control_default_app_name); 844 } 845 846 /** 847 * Send the message along to the radio. 848 * 849 * @param tracker holds the SMS message to send 850 */ 851 protected abstract void sendSms(SmsTracker tracker); 852 853 /** 854 * Send the multi-part SMS based on multipart Sms tracker 855 * 856 * @param tracker holds the multipart Sms tracker ready to be sent 857 */ 858 protected abstract void sendMultipartSms (SmsTracker tracker); 859 860 /** 861 * Activate or deactivate cell broadcast SMS. 862 * 863 * @param activate 864 * 0 = activate, 1 = deactivate 865 * @param response 866 * Callback message is empty on completion 867 */ 868 protected abstract void activateCellBroadcastSms(int activate, Message response); 869 870 /** 871 * Query the current configuration of cell broadcast SMS. 872 * 873 * @param response 874 * Callback message contains the configuration from the modem on completion 875 * @see #setCellBroadcastConfig 876 */ 877 protected abstract void getCellBroadcastSmsConfig(Message response); 878 879 /** 880 * Configure cell broadcast SMS. 881 * 882 * @param configValuesArray 883 * The first element defines the number of triples that follow. 884 * A triple is made up of the service category, the language identifier 885 * and a boolean that specifies whether the category is set active. 886 * @param response 887 * Callback message is empty on completion 888 */ 889 protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response); 890 891 /** 892 * Send an acknowledge message. 893 * @param success indicates that last message was successfully received. 894 * @param result result code indicating any error 895 * @param response callback message sent when operation completes. 896 */ 897 protected abstract void acknowledgeLastIncomingSms(boolean success, 898 int result, Message response); 899 900 /** 901 * Notify interested apps if the framework has rejected an incoming SMS, 902 * and send an acknowledge message to the network. 903 * @param success indicates that last message was successfully received. 904 * @param result result code indicating any error 905 * @param response callback message sent when operation completes. 906 */ 907 private void notifyAndAcknowledgeLastIncomingSms(boolean success, 908 int result, Message response) { 909 if (!success) { 910 // broadcast SMS_REJECTED_ACTION intent 911 Intent intent = new Intent(Intents.SMS_REJECTED_ACTION); 912 intent.putExtra("result", result); 913 mWakeLock.acquire(WAKE_LOCK_TIMEOUT); 914 mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS"); 915 } 916 acknowledgeLastIncomingSms(success, result, response); 917 } 918 919 /** 920 * Check if a SmsTracker holds multi-part Sms 921 * 922 * @param tracker a SmsTracker could hold a multi-part Sms 923 * @return true for tracker holds Multi-parts Sms 924 */ 925 private boolean isMultipartTracker (SmsTracker tracker) { 926 HashMap map = tracker.mData; 927 return ( map.get("parts") != null); 928 } 929 930 /** 931 * Keeps track of an SMS that has been sent to the RIL, until it has 932 * successfully been sent, or we're done trying. 933 * 934 */ 935 static protected class SmsTracker { 936 // fields need to be public for derived SmsDispatchers 937 public HashMap<String, Object> mData; 938 public int mRetryCount; 939 public int mMessageRef; 940 941 public PendingIntent mSentIntent; 942 public PendingIntent mDeliveryIntent; 943 944 SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent, 945 PendingIntent deliveryIntent) { 946 mData = data; 947 mSentIntent = sentIntent; 948 mDeliveryIntent = deliveryIntent; 949 mRetryCount = 0; 950 } 951 } 952 953 protected SmsTracker SmsTrackerFactory(HashMap<String, Object> data, PendingIntent sentIntent, 954 PendingIntent deliveryIntent) { 955 return new SmsTracker(data, sentIntent, deliveryIntent); 956 } 957 958 private DialogInterface.OnClickListener mListener = 959 new DialogInterface.OnClickListener() { 960 961 public void onClick(DialogInterface dialog, int which) { 962 if (which == DialogInterface.BUTTON_POSITIVE) { 963 Log.d(TAG, "click YES to send out sms"); 964 sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS)); 965 } else if (which == DialogInterface.BUTTON_NEGATIVE) { 966 Log.d(TAG, "click NO to stop sending"); 967 sendMessage(obtainMessage(EVENT_STOP_SENDING)); 968 } 969 } 970 }; 971 972 private BroadcastReceiver mResultReceiver = new BroadcastReceiver() { 973 @Override 974 public void onReceive(Context context, Intent intent) { 975 if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) { 976 mStorageAvailable = false; 977 mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); 978 } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) { 979 mStorageAvailable = true; 980 mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); 981 } else { 982 // Assume the intent is one of the SMS receive intents that 983 // was sent as an ordered broadcast. Check result and ACK. 984 int rc = getResultCode(); 985 boolean success = (rc == Activity.RESULT_OK) 986 || (rc == Intents.RESULT_SMS_HANDLED); 987 988 // For a multi-part message, this only ACKs the last part. 989 // Previous parts were ACK'd as they were received. 990 acknowledgeLastIncomingSms(success, rc, null); 991 } 992 } 993 }; 994 995 protected abstract void handleBroadcastSms(AsyncResult ar); 996 997 protected void dispatchBroadcastPdus(byte[][] pdus) { 998 Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED"); 999 intent.putExtra("pdus", pdus); 1000 1001 if (Config.LOGD) 1002 Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); 1003 1004 dispatch(intent, "android.permission.RECEIVE_SMS"); 1005 } 1006 1007} 1008