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