SMSDispatcher.java revision ec125af4306f1be2c829c3a1d7adbf9b8cd7e659
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.AlertDialog; 21import android.app.PendingIntent; 22import android.app.PendingIntent.CanceledException; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.Intent; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.PackageInfo; 29import android.content.pm.PackageManager; 30import android.content.res.Resources; 31import android.database.ContentObserver; 32import android.os.AsyncResult; 33import android.os.Binder; 34import android.os.Handler; 35import android.os.Message; 36import android.os.SystemProperties; 37import android.provider.Settings; 38import android.telephony.PhoneNumberUtils; 39import android.telephony.Rlog; 40import android.telephony.ServiceState; 41import android.telephony.TelephonyManager; 42import android.text.Html; 43import android.text.Spanned; 44import android.util.EventLog; 45import android.view.LayoutInflater; 46import android.view.View; 47import android.view.ViewGroup; 48import android.view.WindowManager; 49import android.widget.Button; 50import android.widget.CheckBox; 51import android.widget.CompoundButton; 52import android.widget.TextView; 53 54import com.android.internal.R; 55import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 56 57import java.util.ArrayList; 58import java.util.HashMap; 59import java.util.Random; 60import java.util.concurrent.atomic.AtomicInteger; 61 62import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE; 63import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE; 64import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED; 65import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE; 66import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU; 67import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF; 68 69public abstract class SMSDispatcher extends Handler { 70 static final String TAG = "SMSDispatcher"; // accessed from inner class 71 static final boolean DBG = false; 72 private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg"; 73 74 /** Permission required to send SMS to short codes without user confirmation. */ 75 private static final String SEND_SMS_NO_CONFIRMATION_PERMISSION = 76 "android.permission.SEND_SMS_NO_CONFIRMATION"; 77 78 private static final int PREMIUM_RULE_USE_SIM = 1; 79 private static final int PREMIUM_RULE_USE_NETWORK = 2; 80 private static final int PREMIUM_RULE_USE_BOTH = 3; 81 private final AtomicInteger mPremiumSmsRule = new AtomicInteger(PREMIUM_RULE_USE_SIM); 82 private final SettingsObserver mSettingsObserver; 83 84 /** SMS send complete. */ 85 protected static final int EVENT_SEND_SMS_COMPLETE = 2; 86 87 /** Retry sending a previously failed SMS message */ 88 private static final int EVENT_SEND_RETRY = 3; 89 90 /** Confirmation required for sending a large number of messages. */ 91 private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4; 92 93 /** Send the user confirmed SMS */ 94 static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class 95 96 /** Don't send SMS (user did not confirm). */ 97 static final int EVENT_STOP_SENDING = 7; // accessed from inner class 98 99 /** Confirmation required for third-party apps sending to an SMS short code. */ 100 private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8; 101 102 /** Confirmation required for third-party apps sending to an SMS short code. */ 103 private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9; 104 105 /** Handle status report from {@code CdmaInboundSmsHandler}. */ 106 protected static final int EVENT_HANDLE_STATUS_REPORT = 10; 107 108 protected final Phone mPhone; 109 protected final Context mContext; 110 protected final ContentResolver mResolver; 111 protected final CommandsInterface mCi; 112 protected final TelephonyManager mTelephonyManager; 113 114 /** Maximum number of times to retry sending a failed SMS. */ 115 private static final int MAX_SEND_RETRIES = 3; 116 /** Delay before next send attempt on a failed SMS, in milliseconds. */ 117 private static final int SEND_RETRY_DELAY = 2000; 118 /** single part SMS */ 119 private static final int SINGLE_PART_SMS = 1; 120 /** Message sending queue limit */ 121 private static final int MO_MSG_QUEUE_LIMIT = 5; 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 * Static field shared by all dispatcher objects. 128 */ 129 private static int sConcatenatedRef = new Random().nextInt(256); 130 131 /** Outgoing message counter. Shared by all dispatchers. */ 132 private final SmsUsageMonitor mUsageMonitor; 133 134 /** Number of outgoing SmsTrackers waiting for user confirmation. */ 135 private int mPendingTrackerCount; 136 137 /* Flags indicating whether the current device allows sms service */ 138 protected boolean mSmsCapable = true; 139 protected boolean mSmsSendDisabled; 140 141 protected int mRemainingMessages = -1; 142 143 protected static int getNextConcatenatedRef() { 144 sConcatenatedRef += 1; 145 return sConcatenatedRef; 146 } 147 148 /** 149 * Create a new SMS dispatcher. 150 * @param phone the Phone to use 151 * @param usageMonitor the SmsUsageMonitor to use 152 */ 153 protected SMSDispatcher(PhoneBase phone, SmsUsageMonitor usageMonitor) { 154 mPhone = phone; 155 mContext = phone.getContext(); 156 mResolver = mContext.getContentResolver(); 157 mCi = phone.mCi; 158 mUsageMonitor = usageMonitor; 159 mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 160 mSettingsObserver = new SettingsObserver(this, mPremiumSmsRule, mContext); 161 mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( 162 Settings.Global.SMS_SHORT_CODE_RULE), false, mSettingsObserver); 163 164 mSmsCapable = mContext.getResources().getBoolean( 165 com.android.internal.R.bool.config_sms_capable); 166 mSmsSendDisabled = !SystemProperties.getBoolean( 167 TelephonyProperties.PROPERTY_SMS_SEND, mSmsCapable); 168 Rlog.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat() 169 + " mSmsSendDisabled=" + mSmsSendDisabled); 170 } 171 172 /** 173 * Observe the secure setting for updated premium sms determination rules 174 */ 175 private static class SettingsObserver extends ContentObserver { 176 private final AtomicInteger mPremiumSmsRule; 177 private final Context mContext; 178 SettingsObserver(Handler handler, AtomicInteger premiumSmsRule, Context context) { 179 super(handler); 180 mPremiumSmsRule = premiumSmsRule; 181 mContext = context; 182 onChange(false); // load initial value; 183 } 184 185 @Override 186 public void onChange(boolean selfChange) { 187 mPremiumSmsRule.set(Settings.Global.getInt(mContext.getContentResolver(), 188 Settings.Global.SMS_SHORT_CODE_RULE, PREMIUM_RULE_USE_SIM)); 189 } 190 } 191 192 /** Unregister for incoming SMS events. */ 193 public void dispose() { 194 mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); 195 } 196 197 /** 198 * The format of the message PDU in the associated broadcast intent. 199 * This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format 200 * or "3gpp2" for CDMA/LTE messages in 3GPP2 format. 201 * 202 * Note: All applications which handle incoming SMS messages by processing the 203 * SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent 204 * into the new methods in {@link android.telephony.SmsMessage} which take an 205 * extra format parameter. This is required in order to correctly decode the PDU on 206 * devices which require support for both 3GPP and 3GPP2 formats at the same time, 207 * such as CDMA/LTE devices and GSM/CDMA world phones. 208 * 209 * @return the format of the message PDU 210 */ 211 protected abstract String getFormat(); 212 213 /** 214 * Pass the Message object to subclass to handle. Currently used to pass CDMA status reports 215 * from {@link com.android.internal.telephony.cdma.CdmaInboundSmsHandler}. 216 * @param o the SmsMessage containing the status report 217 */ 218 protected void handleStatusReport(Object o) { 219 Rlog.d(TAG, "handleStatusReport() called with no subclass."); 220 } 221 222 /* TODO: Need to figure out how to keep track of status report routing in a 223 * persistent manner. If the phone process restarts (reboot or crash), 224 * we will lose this list and any status reports that come in after 225 * will be dropped. 226 */ 227 /** Sent messages awaiting a delivery status report. */ 228 protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>(); 229 230 /** 231 * Handles events coming from the phone stack. Overridden from handler. 232 * 233 * @param msg the message to handle 234 */ 235 @Override 236 public void handleMessage(Message msg) { 237 switch (msg.what) { 238 case EVENT_SEND_SMS_COMPLETE: 239 // An outbound SMS has been successfully transferred, or failed. 240 handleSendComplete((AsyncResult) msg.obj); 241 break; 242 243 case EVENT_SEND_RETRY: 244 sendSms((SmsTracker) msg.obj); 245 break; 246 247 case EVENT_SEND_LIMIT_REACHED_CONFIRMATION: 248 handleReachSentLimit((SmsTracker)(msg.obj)); 249 break; 250 251 case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE: 252 handleConfirmShortCode(false, (SmsTracker)(msg.obj)); 253 break; 254 255 case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE: 256 handleConfirmShortCode(true, (SmsTracker)(msg.obj)); 257 break; 258 259 case EVENT_SEND_CONFIRMED_SMS: 260 { 261 SmsTracker tracker = (SmsTracker) msg.obj; 262 if (tracker.isMultipart()) { 263 sendMultipartSms(tracker); 264 } else { 265 sendSms(tracker); 266 } 267 mPendingTrackerCount--; 268 break; 269 } 270 271 case EVENT_STOP_SENDING: 272 { 273 SmsTracker tracker = (SmsTracker) msg.obj; 274 if (tracker.mSentIntent != null) { 275 try { 276 tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); 277 } catch (CanceledException ex) { 278 Rlog.e(TAG, "failed to send RESULT_ERROR_LIMIT_EXCEEDED"); 279 } 280 } 281 mPendingTrackerCount--; 282 break; 283 } 284 285 case EVENT_HANDLE_STATUS_REPORT: 286 handleStatusReport(msg.obj); 287 break; 288 289 default: 290 Rlog.e(TAG, "handleMessage() ignoring message of unexpected type " + msg.what); 291 } 292 } 293 294 /** 295 * Called when SMS send completes. Broadcasts a sentIntent on success. 296 * On failure, either sets up retries or broadcasts a sentIntent with 297 * the failure in the result code. 298 * 299 * @param ar AsyncResult passed into the message handler. ar.result should 300 * an SmsResponse instance if send was successful. ar.userObj 301 * should be an SmsTracker instance. 302 */ 303 protected void handleSendComplete(AsyncResult ar) { 304 SmsTracker tracker = (SmsTracker) ar.userObj; 305 PendingIntent sentIntent = tracker.mSentIntent; 306 307 if (ar.result != null) { 308 tracker.mMessageRef = ((SmsResponse)ar.result).mMessageRef; 309 } else { 310 Rlog.d(TAG, "SmsResponse was null"); 311 } 312 313 if (ar.exception == null) { 314 if (DBG) Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent); 315 316 if (tracker.mDeliveryIntent != null) { 317 // Expecting a status report. Add it to the list. 318 deliveryPendingList.add(tracker); 319 } 320 321 if (sentIntent != null) { 322 try { 323 if (mRemainingMessages > -1) { 324 mRemainingMessages--; 325 } 326 327 if (mRemainingMessages == 0) { 328 Intent sendNext = new Intent(); 329 sendNext.putExtra(SEND_NEXT_MSG_EXTRA, true); 330 sentIntent.send(mContext, Activity.RESULT_OK, sendNext); 331 } else { 332 sentIntent.send(Activity.RESULT_OK); 333 } 334 } catch (CanceledException ex) {} 335 } 336 } else { 337 if (DBG) Rlog.d(TAG, "SMS send failed"); 338 339 int ss = mPhone.getServiceState().getState(); 340 341 if (ss != ServiceState.STATE_IN_SERVICE) { 342 handleNotInService(ss, tracker.mSentIntent); 343 } else if ((((CommandException)(ar.exception)).getCommandError() 344 == CommandException.Error.SMS_FAIL_RETRY) && 345 tracker.mRetryCount < MAX_SEND_RETRIES) { 346 // Retry after a delay if needed. 347 // TODO: According to TS 23.040, 9.2.3.6, we should resend 348 // with the same TP-MR as the failed message, and 349 // TP-RD set to 1. However, we don't have a means of 350 // knowing the MR for the failed message (EF_SMSstatus 351 // may or may not have the MR corresponding to this 352 // message, depending on the failure). Also, in some 353 // implementations this retry is handled by the baseband. 354 tracker.mRetryCount++; 355 Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker); 356 sendMessageDelayed(retryMsg, SEND_RETRY_DELAY); 357 } else if (tracker.mSentIntent != null) { 358 int error = RESULT_ERROR_GENERIC_FAILURE; 359 360 if (((CommandException)(ar.exception)).getCommandError() 361 == CommandException.Error.FDN_CHECK_FAILURE) { 362 error = RESULT_ERROR_FDN_CHECK_FAILURE; 363 } 364 // Done retrying; return an error to the app. 365 try { 366 Intent fillIn = new Intent(); 367 if (ar.result != null) { 368 fillIn.putExtra("errorCode", ((SmsResponse)ar.result).mErrorCode); 369 } 370 if (mRemainingMessages > -1) { 371 mRemainingMessages--; 372 } 373 374 if (mRemainingMessages == 0) { 375 fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true); 376 } 377 378 tracker.mSentIntent.send(mContext, error, fillIn); 379 } catch (CanceledException ex) {} 380 } 381 } 382 } 383 384 /** 385 * Handles outbound message when the phone is not in service. 386 * 387 * @param ss Current service state. Valid values are: 388 * OUT_OF_SERVICE 389 * EMERGENCY_ONLY 390 * POWER_OFF 391 * @param sentIntent the PendingIntent to send the error to 392 */ 393 protected static void handleNotInService(int ss, PendingIntent sentIntent) { 394 if (sentIntent != null) { 395 try { 396 if (ss == ServiceState.STATE_POWER_OFF) { 397 sentIntent.send(RESULT_ERROR_RADIO_OFF); 398 } else { 399 sentIntent.send(RESULT_ERROR_NO_SERVICE); 400 } 401 } catch (CanceledException ex) {} 402 } 403 } 404 405 /** 406 * Send a data based SMS to a specific application port. 407 * 408 * @param destAddr the address to send the message to 409 * @param scAddr is the service center address or null to use 410 * the current default SMSC 411 * @param destPort the port to deliver the message to 412 * @param data the body of the message to send 413 * @param sentIntent if not NULL this <code>PendingIntent</code> is 414 * broadcast when the message is successfully sent, or failed. 415 * The result code will be <code>Activity.RESULT_OK<code> for success, 416 * or one of these errors:<br> 417 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> 418 * <code>RESULT_ERROR_RADIO_OFF</code><br> 419 * <code>RESULT_ERROR_NULL_PDU</code><br> 420 * <code>RESULT_ERROR_NO_SERVICE</code><br>. 421 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include 422 * the extra "errorCode" containing a radio technology specific value, 423 * generally only useful for troubleshooting.<br> 424 * The per-application based SMS control checks sentIntent. If sentIntent 425 * is NULL the caller will be checked against all unknown applications, 426 * which cause smaller number of SMS to be sent in checking period. 427 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is 428 * broadcast when the message is delivered to the recipient. The 429 * raw pdu of the status report is in the extended data ("pdu"). 430 */ 431 protected abstract void sendData(String destAddr, String scAddr, int destPort, 432 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent); 433 434 /** 435 * Send a text based SMS. 436 * 437 * @param destAddr the address to send the message to 438 * @param scAddr is the service center address or null to use 439 * the current default SMSC 440 * @param text the body of the message to send 441 * @param sentIntent if not NULL this <code>PendingIntent</code> is 442 * broadcast when the message is successfully sent, or failed. 443 * The result code will be <code>Activity.RESULT_OK<code> for success, 444 * or one of these errors:<br> 445 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> 446 * <code>RESULT_ERROR_RADIO_OFF</code><br> 447 * <code>RESULT_ERROR_NULL_PDU</code><br> 448 * <code>RESULT_ERROR_NO_SERVICE</code><br>. 449 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include 450 * the extra "errorCode" containing a radio technology specific value, 451 * generally only useful for troubleshooting.<br> 452 * The per-application based SMS control checks sentIntent. If sentIntent 453 * is NULL the caller will be checked against all unknown applications, 454 * which cause smaller number of SMS to be sent in checking period. 455 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is 456 * broadcast when the message is delivered to the recipient. The 457 * raw pdu of the status report is in the extended data ("pdu"). 458 */ 459 protected abstract void sendText(String destAddr, String scAddr, 460 String text, PendingIntent sentIntent, PendingIntent deliveryIntent); 461 462 /** 463 * Calculate the number of septets needed to encode the message. 464 * 465 * @param messageBody the message to encode 466 * @param use7bitOnly ignore (but still count) illegal characters if true 467 * @return TextEncodingDetails 468 */ 469 protected abstract TextEncodingDetails calculateLength(CharSequence messageBody, 470 boolean use7bitOnly); 471 472 /** 473 * Send a multi-part text based SMS. 474 * 475 * @param destAddr the address to send the message to 476 * @param scAddr is the service center address or null to use 477 * the current default SMSC 478 * @param parts an <code>ArrayList</code> of strings that, in order, 479 * comprise the original message 480 * @param sentIntents if not null, an <code>ArrayList</code> of 481 * <code>PendingIntent</code>s (one for each message part) that is 482 * broadcast when the corresponding message part has been sent. 483 * The result code will be <code>Activity.RESULT_OK<code> for success, 484 * or one of these errors: 485 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 486 * <code>RESULT_ERROR_RADIO_OFF</code> 487 * <code>RESULT_ERROR_NULL_PDU</code> 488 * <code>RESULT_ERROR_NO_SERVICE</code>. 489 * The per-application based SMS control checks sentIntent. If sentIntent 490 * is NULL the caller will be checked against all unknown applications, 491 * which cause smaller number of SMS to be sent in checking period. 492 * @param deliveryIntents if not null, an <code>ArrayList</code> of 493 * <code>PendingIntent</code>s (one for each message part) that is 494 * broadcast when the corresponding message part has been delivered 495 * to the recipient. The raw pdu of the status report is in the 496 * extended data ("pdu"). 497 */ 498 protected void sendMultipartText(String destAddr, String scAddr, 499 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 500 ArrayList<PendingIntent> deliveryIntents) { 501 502 int refNumber = getNextConcatenatedRef() & 0x00FF; 503 int msgCount = parts.size(); 504 int encoding = SmsConstants.ENCODING_UNKNOWN; 505 506 mRemainingMessages = msgCount; 507 508 TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount]; 509 for (int i = 0; i < msgCount; i++) { 510 TextEncodingDetails details = calculateLength(parts.get(i), false); 511 if (encoding != details.codeUnitSize 512 && (encoding == SmsConstants.ENCODING_UNKNOWN 513 || encoding == SmsConstants.ENCODING_7BIT)) { 514 encoding = details.codeUnitSize; 515 } 516 encodingForParts[i] = details; 517 } 518 519 for (int i = 0; i < msgCount; i++) { 520 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); 521 concatRef.refNumber = refNumber; 522 concatRef.seqNumber = i + 1; // 1-based sequence 523 concatRef.msgCount = msgCount; 524 // TODO: We currently set this to true since our messaging app will never 525 // send more than 255 parts (it converts the message to MMS well before that). 526 // However, we should support 3rd party messaging apps that might need 16-bit 527 // references 528 // Note: It's not sufficient to just flip this bit to true; it will have 529 // ripple effects (several calculations assume 8-bit ref). 530 concatRef.isEightBits = true; 531 SmsHeader smsHeader = new SmsHeader(); 532 smsHeader.concatRef = concatRef; 533 534 // Set the national language tables for 3GPP 7-bit encoding, if enabled. 535 if (encoding == SmsConstants.ENCODING_7BIT) { 536 smsHeader.languageTable = encodingForParts[i].languageTable; 537 smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable; 538 } 539 540 PendingIntent sentIntent = null; 541 if (sentIntents != null && sentIntents.size() > i) { 542 sentIntent = sentIntents.get(i); 543 } 544 545 PendingIntent deliveryIntent = null; 546 if (deliveryIntents != null && deliveryIntents.size() > i) { 547 deliveryIntent = deliveryIntents.get(i); 548 } 549 550 sendNewSubmitPdu(destAddr, scAddr, parts.get(i), smsHeader, encoding, 551 sentIntent, deliveryIntent, (i == (msgCount - 1))); 552 } 553 554 } 555 556 /** 557 * Create a new SubmitPdu and send it. 558 */ 559 protected abstract void sendNewSubmitPdu(String destinationAddress, String scAddress, 560 String message, SmsHeader smsHeader, int encoding, 561 PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart); 562 563 /** 564 * Send a SMS 565 * 566 * @param smsc the SMSC to send the message through, or NULL for the 567 * default SMSC 568 * @param pdu the raw PDU to send 569 * @param sentIntent if not NULL this <code>Intent</code> is 570 * broadcast when the message is successfully sent, or failed. 571 * The result code will be <code>Activity.RESULT_OK<code> for success, 572 * or one of these errors: 573 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 574 * <code>RESULT_ERROR_RADIO_OFF</code> 575 * <code>RESULT_ERROR_NULL_PDU</code> 576 * <code>RESULT_ERROR_NO_SERVICE</code>. 577 * The per-application based SMS control checks sentIntent. If sentIntent 578 * is NULL the caller will be checked against all unknown applications, 579 * which cause smaller number of SMS to be sent in checking period. 580 * @param deliveryIntent if not NULL this <code>Intent</code> is 581 * broadcast when the message is delivered to the recipient. The 582 * raw pdu of the status report is in the extended data ("pdu"). 583 * @param destAddr the destination phone number (for short code confirmation) 584 */ 585 protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, 586 PendingIntent deliveryIntent, String destAddr) { 587 if (mSmsSendDisabled) { 588 if (sentIntent != null) { 589 try { 590 sentIntent.send(RESULT_ERROR_NO_SERVICE); 591 } catch (CanceledException ex) {} 592 } 593 Rlog.d(TAG, "Device does not support sending sms."); 594 return; 595 } 596 597 if (pdu == null) { 598 if (sentIntent != null) { 599 try { 600 sentIntent.send(RESULT_ERROR_NULL_PDU); 601 } catch (CanceledException ex) {} 602 } 603 return; 604 } 605 606 HashMap<String, Object> map = new HashMap<String, Object>(); 607 map.put("smsc", smsc); 608 map.put("pdu", pdu); 609 610 // Get calling app package name via UID from Binder call 611 PackageManager pm = mContext.getPackageManager(); 612 String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid()); 613 614 if (packageNames == null || packageNames.length == 0) { 615 // Refuse to send SMS if we can't get the calling package name. 616 Rlog.e(TAG, "Can't get calling app package name: refusing to send SMS"); 617 if (sentIntent != null) { 618 try { 619 sentIntent.send(RESULT_ERROR_GENERIC_FAILURE); 620 } catch (CanceledException ex) { 621 Rlog.e(TAG, "failed to send error result"); 622 } 623 } 624 return; 625 } 626 627 // Get package info via packagemanager 628 PackageInfo appInfo; 629 try { 630 // XXX this is lossy- apps can share a UID 631 appInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES); 632 } catch (PackageManager.NameNotFoundException e) { 633 Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS"); 634 if (sentIntent != null) { 635 try { 636 sentIntent.send(RESULT_ERROR_GENERIC_FAILURE); 637 } catch (CanceledException ex) { 638 Rlog.e(TAG, "failed to send error result"); 639 } 640 } 641 return; 642 } 643 644 // Strip non-digits from destination phone number before checking for short codes 645 // and before displaying the number to the user if confirmation is required. 646 SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appInfo, 647 PhoneNumberUtils.extractNetworkPortion(destAddr)); 648 649 // checkDestination() returns true if the destination is not a premium short code or the 650 // sending app is approved to send to short codes. Otherwise, a message is sent to our 651 // handler with the SmsTracker to request user confirmation before sending. 652 if (checkDestination(tracker)) { 653 // check for excessive outgoing SMS usage by this app 654 if (!mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS)) { 655 sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker)); 656 return; 657 } 658 659 int ss = mPhone.getServiceState().getState(); 660 661 if (ss != ServiceState.STATE_IN_SERVICE) { 662 handleNotInService(ss, tracker.mSentIntent); 663 } else { 664 sendSms(tracker); 665 } 666 } 667 } 668 669 /** 670 * Check if destination is a potential premium short code and sender is not pre-approved to 671 * send to short codes. 672 * 673 * @param tracker the tracker for the SMS to send 674 * @return true if the destination is approved; false if user confirmation event was sent 675 */ 676 boolean checkDestination(SmsTracker tracker) { 677 if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION_PERMISSION) 678 == PackageManager.PERMISSION_GRANTED) { 679 return true; // app is pre-approved to send to short codes 680 } else { 681 int rule = mPremiumSmsRule.get(); 682 int smsCategory = SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE; 683 if (rule == PREMIUM_RULE_USE_SIM || rule == PREMIUM_RULE_USE_BOTH) { 684 String simCountryIso = mTelephonyManager.getSimCountryIso(); 685 if (simCountryIso == null || simCountryIso.length() != 2) { 686 Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso"); 687 simCountryIso = mTelephonyManager.getNetworkCountryIso(); 688 } 689 690 smsCategory = mUsageMonitor.checkDestination(tracker.mDestAddress, simCountryIso); 691 } 692 if (rule == PREMIUM_RULE_USE_NETWORK || rule == PREMIUM_RULE_USE_BOTH) { 693 String networkCountryIso = mTelephonyManager.getNetworkCountryIso(); 694 if (networkCountryIso == null || networkCountryIso.length() != 2) { 695 Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso"); 696 networkCountryIso = mTelephonyManager.getSimCountryIso(); 697 } 698 699 smsCategory = SmsUsageMonitor.mergeShortCodeCategories(smsCategory, 700 mUsageMonitor.checkDestination(tracker.mDestAddress, networkCountryIso)); 701 } 702 703 if (smsCategory == SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE 704 || smsCategory == SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE 705 || smsCategory == SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE) { 706 return true; // not a premium short code 707 } 708 709 // Wait for user confirmation unless the user has set permission to always allow/deny 710 int premiumSmsPermission = mUsageMonitor.getPremiumSmsPermission( 711 tracker.mAppInfo.packageName); 712 if (premiumSmsPermission == SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) { 713 // First time trying to send to premium SMS. 714 premiumSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER; 715 } 716 717 switch (premiumSmsPermission) { 718 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW: 719 Rlog.d(TAG, "User approved this app to send to premium SMS"); 720 return true; 721 722 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW: 723 Rlog.w(TAG, "User denied this app from sending to premium SMS"); 724 sendMessage(obtainMessage(EVENT_STOP_SENDING, tracker)); 725 return false; // reject this message 726 727 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER: 728 default: 729 int event; 730 if (smsCategory == SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE) { 731 event = EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE; 732 } else { 733 event = EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE; 734 } 735 sendMessage(obtainMessage(event, tracker)); 736 return false; // wait for user confirmation 737 } 738 } 739 } 740 741 /** 742 * Deny sending an SMS if the outgoing queue limit is reached. Used when the message 743 * must be confirmed by the user due to excessive usage or potential premium SMS detected. 744 * @param tracker the SmsTracker for the message to send 745 * @return true if the message was denied; false to continue with send confirmation 746 */ 747 private boolean denyIfQueueLimitReached(SmsTracker tracker) { 748 if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) { 749 // Deny sending message when the queue limit is reached. 750 try { 751 if (tracker.mSentIntent != null) { 752 tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); 753 } 754 } catch (CanceledException ex) { 755 Rlog.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED"); 756 } 757 return true; 758 } 759 mPendingTrackerCount++; 760 return false; 761 } 762 763 /** 764 * Returns the label for the specified app package name. 765 * @param appPackage the package name of the app requesting to send an SMS 766 * @return the label for the specified app, or the package name if getApplicationInfo() fails 767 */ 768 private CharSequence getAppLabel(String appPackage) { 769 PackageManager pm = mContext.getPackageManager(); 770 try { 771 ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0); 772 return appInfo.loadLabel(pm); 773 } catch (PackageManager.NameNotFoundException e) { 774 Rlog.e(TAG, "PackageManager Name Not Found for package " + appPackage); 775 return appPackage; // fall back to package name if we can't get app label 776 } 777 } 778 779 /** 780 * Post an alert when SMS needs confirmation due to excessive usage. 781 * @param tracker an SmsTracker for the current message. 782 */ 783 protected void handleReachSentLimit(SmsTracker tracker) { 784 if (denyIfQueueLimitReached(tracker)) { 785 return; // queue limit reached; error was returned to caller 786 } 787 788 CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName); 789 Resources r = Resources.getSystem(); 790 Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel)); 791 792 ConfirmDialogListener listener = new ConfirmDialogListener(tracker, null); 793 794 AlertDialog d = new AlertDialog.Builder(mContext) 795 .setTitle(R.string.sms_control_title) 796 .setIcon(R.drawable.stat_sys_warning) 797 .setMessage(messageText) 798 .setPositiveButton(r.getString(R.string.sms_control_yes), listener) 799 .setNegativeButton(r.getString(R.string.sms_control_no), listener) 800 .setOnCancelListener(listener) 801 .create(); 802 803 d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 804 d.show(); 805 } 806 807 /** 808 * Post an alert for user confirmation when sending to a potential short code. 809 * @param isPremium true if the destination is known to be a premium short code 810 * @param tracker the SmsTracker for the current message. 811 */ 812 protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) { 813 if (denyIfQueueLimitReached(tracker)) { 814 return; // queue limit reached; error was returned to caller 815 } 816 817 int detailsId; 818 if (isPremium) { 819 detailsId = R.string.sms_premium_short_code_details; 820 } else { 821 detailsId = R.string.sms_short_code_details; 822 } 823 824 CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName); 825 Resources r = Resources.getSystem(); 826 Spanned messageText = Html.fromHtml(r.getString(R.string.sms_short_code_confirm_message, 827 appLabel, tracker.mDestAddress)); 828 829 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 830 Context.LAYOUT_INFLATER_SERVICE); 831 View layout = inflater.inflate(R.layout.sms_short_code_confirmation_dialog, null); 832 833 ConfirmDialogListener listener = new ConfirmDialogListener(tracker, 834 (TextView)layout.findViewById(R.id.sms_short_code_remember_undo_instruction)); 835 836 837 TextView messageView = (TextView) layout.findViewById(R.id.sms_short_code_confirm_message); 838 messageView.setText(messageText); 839 840 ViewGroup detailsLayout = (ViewGroup) layout.findViewById( 841 R.id.sms_short_code_detail_layout); 842 TextView detailsView = (TextView) detailsLayout.findViewById( 843 R.id.sms_short_code_detail_message); 844 detailsView.setText(detailsId); 845 846 CheckBox rememberChoice = (CheckBox) layout.findViewById( 847 R.id.sms_short_code_remember_choice_checkbox); 848 rememberChoice.setOnCheckedChangeListener(listener); 849 850 AlertDialog d = new AlertDialog.Builder(mContext) 851 .setView(layout) 852 .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener) 853 .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener) 854 .setOnCancelListener(listener) 855 .create(); 856 857 d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 858 d.show(); 859 860 listener.setPositiveButton(d.getButton(DialogInterface.BUTTON_POSITIVE)); 861 listener.setNegativeButton(d.getButton(DialogInterface.BUTTON_NEGATIVE)); 862 } 863 864 /** 865 * Returns the premium SMS permission for the specified package. If the package has never 866 * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER} 867 * will be returned. 868 * @param packageName the name of the package to query permission 869 * @return one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN}, 870 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, 871 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or 872 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} 873 */ 874 public int getPremiumSmsPermission(String packageName) { 875 return mUsageMonitor.getPremiumSmsPermission(packageName); 876 } 877 878 /** 879 * Sets the premium SMS permission for the specified package and save the value asynchronously 880 * to persistent storage. 881 * @param packageName the name of the package to set permission 882 * @param permission one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, 883 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or 884 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} 885 */ 886 public void setPremiumSmsPermission(String packageName, int permission) { 887 mUsageMonitor.setPremiumSmsPermission(packageName, permission); 888 } 889 890 /** 891 * Send the message along to the radio. 892 * 893 * @param tracker holds the SMS message to send 894 */ 895 protected abstract void sendSms(SmsTracker tracker); 896 897 /** 898 * Send the multi-part SMS based on multipart Sms tracker 899 * 900 * @param tracker holds the multipart Sms tracker ready to be sent 901 */ 902 private void sendMultipartSms(SmsTracker tracker) { 903 ArrayList<String> parts; 904 ArrayList<PendingIntent> sentIntents; 905 ArrayList<PendingIntent> deliveryIntents; 906 907 HashMap<String, Object> map = tracker.mData; 908 909 String destinationAddress = (String) map.get("destination"); 910 String scAddress = (String) map.get("scaddress"); 911 912 parts = (ArrayList<String>) map.get("parts"); 913 sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents"); 914 deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents"); 915 916 // check if in service 917 int ss = mPhone.getServiceState().getState(); 918 if (ss != ServiceState.STATE_IN_SERVICE) { 919 for (int i = 0, count = parts.size(); i < count; i++) { 920 PendingIntent sentIntent = null; 921 if (sentIntents != null && sentIntents.size() > i) { 922 sentIntent = sentIntents.get(i); 923 } 924 handleNotInService(ss, sentIntent); 925 } 926 return; 927 } 928 929 sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents); 930 } 931 932 /** 933 * Keeps track of an SMS that has been sent to the RIL, until it has 934 * successfully been sent, or we're done trying. 935 */ 936 protected static final class SmsTracker { 937 // fields need to be public for derived SmsDispatchers 938 public final HashMap<String, Object> mData; 939 public int mRetryCount; 940 public int mMessageRef; 941 942 public final PendingIntent mSentIntent; 943 public final PendingIntent mDeliveryIntent; 944 945 public final PackageInfo mAppInfo; 946 public final String mDestAddress; 947 948 public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent, 949 PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr) { 950 mData = data; 951 mSentIntent = sentIntent; 952 mDeliveryIntent = deliveryIntent; 953 mRetryCount = 0; 954 mAppInfo = appInfo; 955 mDestAddress = destAddr; 956 } 957 958 /** 959 * Returns whether this tracker holds a multi-part SMS. 960 * @return true if the tracker holds a multi-part SMS; false otherwise 961 */ 962 boolean isMultipart() { 963 HashMap<String, Object> map = mData; 964 return map.containsKey("parts"); 965 } 966 } 967 968 /** 969 * Dialog listener for SMS confirmation dialog. 970 */ 971 private final class ConfirmDialogListener 972 implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, 973 CompoundButton.OnCheckedChangeListener { 974 975 private final SmsTracker mTracker; 976 private Button mPositiveButton; 977 private Button mNegativeButton; 978 private boolean mRememberChoice; // default is unchecked 979 private final TextView mRememberUndoInstruction; 980 981 ConfirmDialogListener(SmsTracker tracker, TextView textView) { 982 mTracker = tracker; 983 mRememberUndoInstruction = textView; 984 } 985 986 void setPositiveButton(Button button) { 987 mPositiveButton = button; 988 } 989 990 void setNegativeButton(Button button) { 991 mNegativeButton = button; 992 } 993 994 @Override 995 public void onClick(DialogInterface dialog, int which) { 996 // Always set the SMS permission so that Settings will show a permission setting 997 // for the app (it won't be shown until after the app tries to send to a short code). 998 int newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER; 999 1000 if (which == DialogInterface.BUTTON_POSITIVE) { 1001 Rlog.d(TAG, "CONFIRM sending SMS"); 1002 // XXX this is lossy- apps can have more than one signature 1003 EventLog.writeEvent(EventLogTags.EXP_DET_SMS_SENT_BY_USER, 1004 mTracker.mAppInfo.applicationInfo == null ? 1005 -1 : mTracker.mAppInfo.applicationInfo.uid); 1006 sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker)); 1007 if (mRememberChoice) { 1008 newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW; 1009 } 1010 } else if (which == DialogInterface.BUTTON_NEGATIVE) { 1011 Rlog.d(TAG, "DENY sending SMS"); 1012 // XXX this is lossy- apps can have more than one signature 1013 EventLog.writeEvent(EventLogTags.EXP_DET_SMS_DENIED_BY_USER, 1014 mTracker.mAppInfo.applicationInfo == null ? 1015 -1 : mTracker.mAppInfo.applicationInfo.uid); 1016 sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker)); 1017 if (mRememberChoice) { 1018 newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW; 1019 } 1020 } 1021 setPremiumSmsPermission(mTracker.mAppInfo.packageName, newSmsPermission); 1022 } 1023 1024 @Override 1025 public void onCancel(DialogInterface dialog) { 1026 Rlog.d(TAG, "dialog dismissed: don't send SMS"); 1027 sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker)); 1028 } 1029 1030 @Override 1031 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 1032 Rlog.d(TAG, "remember this choice: " + isChecked); 1033 mRememberChoice = isChecked; 1034 if (isChecked) { 1035 mPositiveButton.setText(R.string.sms_short_code_confirm_always_allow); 1036 mNegativeButton.setText(R.string.sms_short_code_confirm_never_allow); 1037 if (mRememberUndoInstruction != null) { 1038 mRememberUndoInstruction. 1039 setText(R.string.sms_short_code_remember_undo_instruction); 1040 mRememberUndoInstruction.setPadding(0,0,0,32); 1041 } 1042 } else { 1043 mPositiveButton.setText(R.string.sms_short_code_confirm_allow); 1044 mNegativeButton.setText(R.string.sms_short_code_confirm_deny); 1045 if (mRememberUndoInstruction != null) { 1046 mRememberUndoInstruction.setText(""); 1047 mRememberUndoInstruction.setPadding(0,0,0,0); 1048 } 1049 } 1050 } 1051 } 1052} 1053