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