SMSDispatcher.java revision 2563a3ac05dd3cf8a07203ae682c243f2e793137
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 dispatchMessage(sms.mWrappedSmsMessage); 293 } else { 294 acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_OUT_OF_MEMORY, null); 295 } 296 } catch (RuntimeException ex) { 297 acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null); 298 } 299 300 break; 301 302 case EVENT_SEND_SMS_COMPLETE: 303 // An outbound SMS has been successfully transferred, or failed. 304 handleSendComplete((AsyncResult) msg.obj); 305 break; 306 307 case EVENT_SEND_RETRY: 308 sendSms((SmsTracker) msg.obj); 309 break; 310 311 case EVENT_NEW_SMS_STATUS_REPORT: 312 handleStatusReport((AsyncResult)msg.obj); 313 break; 314 315 case EVENT_ICC_FULL: 316 handleIccFull(); 317 break; 318 319 case EVENT_POST_ALERT: 320 handleReachSentLimit((SmsTracker)(msg.obj)); 321 break; 322 323 case EVENT_ALERT_TIMEOUT: 324 ((AlertDialog)(msg.obj)).dismiss(); 325 msg.obj = null; 326 mSTracker = null; 327 break; 328 329 case EVENT_SEND_CONFIRMED_SMS: 330 if (mSTracker!=null) { 331 if (isMultipartTracker(mSTracker)) { 332 sendMultipartSms(mSTracker); 333 } else { 334 sendSms(mSTracker); 335 } 336 mSTracker = null; 337 } 338 break; 339 } 340 } 341 342 private void createWakelock() { 343 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 344 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher"); 345 mWakeLock.setReferenceCounted(true); 346 } 347 348 /** 349 * Grabs a wake lock and sends intent as an ordered broadcast. 350 * The resultReceiver will check for errors and ACK/NACK back 351 * to the RIL. 352 * 353 * @param intent intent to broadcast 354 * @param permission Receivers are required to have this permission 355 */ 356 void dispatch(Intent intent, String permission) { 357 // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any 358 // receivers time to take their own wake locks. 359 mWakeLock.acquire(WAKE_LOCK_TIMEOUT); 360 mContext.sendOrderedBroadcast(intent, permission, mResultReceiver, 361 this, Activity.RESULT_OK, null, null); 362 } 363 364 /** 365 * Called when SIM_FULL message is received from the RIL. Notifies interested 366 * parties that SIM storage for SMS messages is full. 367 */ 368 private void handleIccFull(){ 369 // broadcast SIM_FULL intent 370 Intent intent = new Intent(Intents.SIM_FULL_ACTION); 371 mWakeLock.acquire(WAKE_LOCK_TIMEOUT); 372 mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS"); 373 } 374 375 /** 376 * Called when a status report is received. This should correspond to 377 * a previously successful SEND. 378 * 379 * @param ar AsyncResult passed into the message handler. ar.result should 380 * be a String representing the status report PDU, as ASCII hex. 381 */ 382 protected abstract void handleStatusReport(AsyncResult ar); 383 384 /** 385 * Called when SMS send completes. Broadcasts a sentIntent on success. 386 * On failure, either sets up retries or broadcasts a sentIntent with 387 * the failure in the result code. 388 * 389 * @param ar AsyncResult passed into the message handler. ar.result should 390 * an SmsResponse instance if send was successful. ar.userObj 391 * should be an SmsTracker instance. 392 */ 393 protected void handleSendComplete(AsyncResult ar) { 394 SmsTracker tracker = (SmsTracker) ar.userObj; 395 PendingIntent sentIntent = tracker.mSentIntent; 396 397 if (ar.exception == null) { 398 if (Config.LOGD) { 399 Log.d(TAG, "SMS send complete. Broadcasting " 400 + "intent: " + sentIntent); 401 } 402 403 if (tracker.mDeliveryIntent != null) { 404 // Expecting a status report. Add it to the list. 405 int messageRef = ((SmsResponse)ar.result).messageRef; 406 tracker.mMessageRef = messageRef; 407 deliveryPendingList.add(tracker); 408 } 409 410 if (sentIntent != null) { 411 try { 412 sentIntent.send(Activity.RESULT_OK); 413 } catch (CanceledException ex) {} 414 } 415 } else { 416 if (Config.LOGD) { 417 Log.d(TAG, "SMS send failed"); 418 } 419 420 int ss = mPhone.getServiceState().getState(); 421 422 if (ss != ServiceState.STATE_IN_SERVICE) { 423 handleNotInService(ss, tracker); 424 } else if ((((CommandException)(ar.exception)).getCommandError() 425 == CommandException.Error.SMS_FAIL_RETRY) && 426 tracker.mRetryCount < MAX_SEND_RETRIES) { 427 // Retry after a delay if needed. 428 // TODO: According to TS 23.040, 9.2.3.6, we should resend 429 // with the same TP-MR as the failed message, and 430 // TP-RD set to 1. However, we don't have a means of 431 // knowing the MR for the failed message (EF_SMSstatus 432 // may or may not have the MR corresponding to this 433 // message, depending on the failure). Also, in some 434 // implementations this retry is handled by the baseband. 435 tracker.mRetryCount++; 436 Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker); 437 sendMessageDelayed(retryMsg, SEND_RETRY_DELAY); 438 } else if (tracker.mSentIntent != null) { 439 // Done retrying; return an error to the app. 440 try { 441 tracker.mSentIntent.send(RESULT_ERROR_GENERIC_FAILURE); 442 } catch (CanceledException ex) {} 443 } 444 } 445 } 446 447 /** 448 * Handles outbound message when the phone is not in service. 449 * 450 * @param ss Current service state. Valid values are: 451 * OUT_OF_SERVICE 452 * EMERGENCY_ONLY 453 * POWER_OFF 454 * @param tracker An SmsTracker for the current message. 455 */ 456 protected void handleNotInService(int ss, SmsTracker tracker) { 457 if (tracker.mSentIntent != null) { 458 try { 459 if (ss == ServiceState.STATE_POWER_OFF) { 460 tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF); 461 } else { 462 tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE); 463 } 464 } catch (CanceledException ex) {} 465 } 466 } 467 468 /** 469 * Dispatches an incoming SMS messages. 470 * 471 * @param sms the incoming message from the phone 472 */ 473 protected abstract void dispatchMessage(SmsMessageBase sms); 474 475 476 /** 477 * If this is the last part send the parts out to the application, otherwise 478 * the part is stored for later processing. 479 * 480 * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null. 481 */ 482 protected void processMessagePart(SmsMessageBase sms, 483 SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) { 484 485 // Lookup all other related parts 486 StringBuilder where = new StringBuilder("reference_number ="); 487 where.append(concatRef.refNumber); 488 where.append(" AND address = ?"); 489 String[] whereArgs = new String[] {sms.getOriginatingAddress()}; 490 491 byte[][] pdus = null; 492 Cursor cursor = null; 493 try { 494 cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null); 495 int cursorCount = cursor.getCount(); 496 if (cursorCount != concatRef.msgCount - 1) { 497 // We don't have all the parts yet, store this one away 498 ContentValues values = new ContentValues(); 499 values.put("date", new Long(sms.getTimestampMillis())); 500 values.put("pdu", HexDump.toHexString(sms.getPdu())); 501 values.put("address", sms.getOriginatingAddress()); 502 values.put("reference_number", concatRef.refNumber); 503 values.put("count", concatRef.msgCount); 504 values.put("sequence", concatRef.seqNumber); 505 if (portAddrs != null) { 506 values.put("destination_port", portAddrs.destPort); 507 } 508 mResolver.insert(mRawUri, values); 509 acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null); 510 return; 511 } 512 513 // All the parts are in place, deal with them 514 int pduColumn = cursor.getColumnIndex("pdu"); 515 int sequenceColumn = cursor.getColumnIndex("sequence"); 516 517 pdus = new byte[concatRef.msgCount][]; 518 for (int i = 0; i < cursorCount; i++) { 519 cursor.moveToNext(); 520 int cursorSequence = (int)cursor.getLong(sequenceColumn); 521 pdus[cursorSequence - 1] = HexDump.hexStringToByteArray( 522 cursor.getString(pduColumn)); 523 } 524 // This one isn't in the DB, so add it 525 pdus[concatRef.seqNumber - 1] = sms.getPdu(); 526 527 // Remove the parts from the database 528 mResolver.delete(mRawUri, where.toString(), whereArgs); 529 } catch (SQLException e) { 530 Log.e(TAG, "Can't access multipart SMS database", e); 531 // TODO: Would OUT_OF_MEMORY be more appropriate? 532 acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null); 533 return; 534 } finally { 535 if (cursor != null) cursor.close(); 536 } 537 538 /** 539 * TODO(cleanup): The following code has duplicated logic with 540 * the radio-specific dispatchMessage code, which is fragile, 541 * in addition to being redundant. Instead, if this method 542 * maybe returned the reassembled message (or just contents), 543 * the following code (which is not really related to 544 * reconstruction) could be better consolidated. 545 */ 546 547 // Dispatch the PDUs to applications 548 if (portAddrs != null) { 549 if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) { 550 // Build up the data stream 551 ByteArrayOutputStream output = new ByteArrayOutputStream(); 552 for (int i = 0; i < concatRef.msgCount; i++) { 553 SmsMessage msg = SmsMessage.createFromPdu(pdus[i]); 554 byte[] data = msg.getUserData(); 555 output.write(data, 0, data.length); 556 } 557 // Handle the PUSH 558 mWapPush.dispatchWapPdu(output.toByteArray()); 559 } else { 560 // The messages were sent to a port, so concoct a URI for it 561 dispatchPortAddressedPdus(pdus, portAddrs.destPort); 562 } 563 } else { 564 // The messages were not sent to a port 565 dispatchPdus(pdus); 566 } 567 } 568 569 /** 570 * Dispatches standard PDUs to interested applications 571 * 572 * @param pdus The raw PDUs making up the message 573 */ 574 protected void dispatchPdus(byte[][] pdus) { 575 Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION); 576 intent.putExtra("pdus", pdus); 577 dispatch(intent, "android.permission.RECEIVE_SMS"); 578 } 579 580 /** 581 * Dispatches port addressed PDUs to interested applications 582 * 583 * @param pdus The raw PDUs making up the message 584 * @param port The destination port of the messages 585 */ 586 protected void dispatchPortAddressedPdus(byte[][] pdus, int port) { 587 Uri uri = Uri.parse("sms://localhost:" + port); 588 Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri); 589 intent.putExtra("pdus", pdus); 590 dispatch(intent, "android.permission.RECEIVE_SMS"); 591 } 592 593 594 /** 595 * Send a multi-part text based SMS. 596 * 597 * @param destinationAddress the address to send the message to 598 * @param scAddress is the service center address or null to use 599 * the current default SMSC 600 * @param parts an <code>ArrayList</code> of strings that, in order, 601 * comprise the original message 602 * @param sentIntents if not null, an <code>ArrayList</code> of 603 * <code>PendingIntent</code>s (one for each message part) that is 604 * broadcast when the corresponding message part has been sent. 605 * The result code will be <code>Activity.RESULT_OK<code> for success, 606 * or one of these errors: 607 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 608 * <code>RESULT_ERROR_RADIO_OFF</code> 609 * <code>RESULT_ERROR_NULL_PDU</code>. 610 * The per-application based SMS control checks sentIntent. If sentIntent 611 * is NULL the caller will be checked against all unknown applicaitons, 612 * which cause smaller number of SMS to be sent in checking period. 613 * @param deliveryIntents 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 delivered 616 * to the recipient. The raw pdu of the status report is in the 617 * extended data ("pdu"). 618 */ 619 protected abstract void sendMultipartText(String destinationAddress, String scAddress, 620 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 621 ArrayList<PendingIntent> deliveryIntents); 622 623 /** 624 * Send a SMS 625 * 626 * @param smsc the SMSC to send the message through, or NULL for the 627 * defatult SMSC 628 * @param pdu the raw PDU to send 629 * @param sentIntent if not NULL this <code>Intent</code> is 630 * broadcast when the message is sucessfully sent, or failed. 631 * The result code will be <code>Activity.RESULT_OK<code> for success, 632 * or one of these errors: 633 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 634 * <code>RESULT_ERROR_RADIO_OFF</code> 635 * <code>RESULT_ERROR_NULL_PDU</code>. 636 * The per-application based SMS control checks sentIntent. If sentIntent 637 * is NULL the caller will be checked against all unknown applicaitons, 638 * which cause smaller number of SMS to be sent in checking period. 639 * @param deliveryIntent if not NULL this <code>Intent</code> is 640 * broadcast when the message is delivered to the recipient. The 641 * raw pdu of the status report is in the extended data ("pdu"). 642 */ 643 protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, 644 PendingIntent deliveryIntent) { 645 if (pdu == null) { 646 if (sentIntent != null) { 647 try { 648 sentIntent.send(RESULT_ERROR_NULL_PDU); 649 } catch (CanceledException ex) {} 650 } 651 return; 652 } 653 654 HashMap<String, Object> map = new HashMap<String, Object>(); 655 map.put("smsc", smsc); 656 map.put("pdu", pdu); 657 658 SmsTracker tracker = new SmsTracker(map, sentIntent, 659 deliveryIntent); 660 int ss = mPhone.getServiceState().getState(); 661 662 if (ss != ServiceState.STATE_IN_SERVICE) { 663 handleNotInService(ss, tracker); 664 } else { 665 String appName = getAppNameByIntent(sentIntent); 666 if (mCounter.check(appName, SINGLE_PART_SMS)) { 667 sendSms(tracker); 668 } else { 669 sendMessage(obtainMessage(EVENT_POST_ALERT, tracker)); 670 } 671 } 672 } 673 674 /** 675 * Post an alert while SMS needs user confirm. 676 * 677 * An SmsTracker for the current message. 678 */ 679 protected void handleReachSentLimit(SmsTracker tracker) { 680 681 Resources r = Resources.getSystem(); 682 683 String appName = getAppNameByIntent(tracker.mSentIntent); 684 685 AlertDialog d = new AlertDialog.Builder(mContext) 686 .setTitle(r.getString(R.string.sms_control_title)) 687 .setMessage(appName + " " + r.getString(R.string.sms_control_message)) 688 .setPositiveButton(r.getString(R.string.sms_control_yes), mListener) 689 .setNegativeButton(r.getString(R.string.sms_control_no), null) 690 .create(); 691 692 d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 693 d.show(); 694 695 mSTracker = tracker; 696 sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d), 697 DEFAULT_SMS_TIMOUEOUT); 698 } 699 700 protected String getAppNameByIntent(PendingIntent intent) { 701 Resources r = Resources.getSystem(); 702 return (intent != null) ? intent.getTargetPackage() 703 : r.getString(R.string.sms_control_default_app_name); 704 } 705 706 /** 707 * Send the message along to the radio. 708 * 709 * @param tracker holds the SMS message to send 710 */ 711 protected abstract void sendSms(SmsTracker tracker); 712 713 /** 714 * Send the multi-part SMS based on multipart Sms tracker 715 * 716 * @param tracker holds the multipart Sms tracker ready to be sent 717 */ 718 protected abstract void sendMultipartSms (SmsTracker tracker); 719 720 /** 721 * Activate or deactivate cell broadcast SMS. 722 * 723 * @param activate 724 * 0 = activate, 1 = deactivate 725 * @param response 726 * Callback message is empty on completion 727 */ 728 protected abstract void activateCellBroadcastSms(int activate, Message response); 729 730 /** 731 * Query the current configuration of cell broadcast SMS. 732 * 733 * @param response 734 * Callback message contains the configuration from the modem on completion 735 * @see #setCellBroadcastConfig 736 */ 737 protected abstract void getCellBroadcastSmsConfig(Message response); 738 739 /** 740 * Configure cell broadcast SMS. 741 * 742 * @param configValuesArray 743 * The first element defines the number of triples that follow. 744 * A triple is made up of the service category, the language identifier 745 * and a boolean that specifies whether the category is set active. 746 * @param response 747 * Callback message is empty on completion 748 */ 749 protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response); 750 751 /** 752 * Send an acknowledge message. 753 * @param success indicates that last message was successfully received. 754 * @param result result code indicating any error 755 * @param response callback message sent when operation completes. 756 */ 757 protected abstract void acknowledgeLastIncomingSms(boolean success, 758 int result, Message response); 759 760 /** 761 * Check if a SmsTracker holds multi-part Sms 762 * 763 * @param tracker a SmsTracker could hold a multi-part Sms 764 * @return true for tracker holds Multi-parts Sms 765 */ 766 private boolean isMultipartTracker (SmsTracker tracker) { 767 HashMap map = tracker.mData; 768 return ( map.get("parts") != null); 769 } 770 771 /** 772 * Keeps track of an SMS that has been sent to the RIL, until it it has 773 * successfully been sent, or we're done trying. 774 * 775 */ 776 static protected class SmsTracker { 777 // fields need to be public for derived SmsDispatchers 778 public HashMap mData; 779 public int mRetryCount; 780 public int mMessageRef; 781 782 public PendingIntent mSentIntent; 783 public PendingIntent mDeliveryIntent; 784 785 SmsTracker(HashMap data, PendingIntent sentIntent, 786 PendingIntent deliveryIntent) { 787 mData = data; 788 mSentIntent = sentIntent; 789 mDeliveryIntent = deliveryIntent; 790 mRetryCount = 0; 791 } 792 } 793 794 protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent, 795 PendingIntent deliveryIntent) { 796 return new SmsTracker(data, sentIntent, deliveryIntent); 797 } 798 799 private DialogInterface.OnClickListener mListener = 800 new DialogInterface.OnClickListener() { 801 802 public void onClick(DialogInterface dialog, int which) { 803 if (which == DialogInterface.BUTTON_POSITIVE) { 804 Log.d(TAG, "click YES to send out sms"); 805 sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS)); 806 } 807 } 808 }; 809 810 private BroadcastReceiver mResultReceiver = new BroadcastReceiver() { 811 @Override 812 public void onReceive(Context context, Intent intent) { 813 if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) { 814 mStorageAvailable = false; 815 mCm.reportSmsMemoryStatus(false, null); 816 } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) { 817 mStorageAvailable = true; 818 mCm.reportSmsMemoryStatus(true, null); 819 } else { 820 // Assume the intent is one of the SMS receive intents that 821 // was sent as an ordered broadcast. Check result and ACK. 822 int rc = getResultCode(); 823 boolean success = (rc == Activity.RESULT_OK) 824 || (rc == Intents.RESULT_SMS_HANDLED); 825 826 // For a multi-part message, this only ACKs the last part. 827 // Previous parts were ACK'd as they were received. 828 acknowledgeLastIncomingSms(success, rc, null); 829 } 830 } 831 832 }; 833} 834