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