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