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