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