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 static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED; 20import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; 21 22import android.app.Activity; 23import android.app.PendingIntent; 24import android.app.PendingIntent.CanceledException; 25import android.content.Context; 26import android.content.Intent; 27import android.net.Uri; 28import android.os.AsyncResult; 29import android.os.Handler; 30import android.os.Message; 31import android.provider.Telephony.Sms; 32import android.provider.Telephony.Sms.Intents; 33import android.telephony.Rlog; 34import android.telephony.SmsManager; 35import android.telephony.SmsMessage; 36import android.util.Pair; 37 38import com.android.internal.annotations.VisibleForTesting; 39import com.android.internal.telephony.cdma.CdmaInboundSmsHandler; 40import com.android.internal.telephony.cdma.CdmaSMSDispatcher; 41import com.android.internal.telephony.gsm.GsmInboundSmsHandler; 42import com.android.internal.telephony.gsm.GsmSMSDispatcher; 43 44import java.util.ArrayList; 45import java.util.HashMap; 46 47/** 48 * 49 */ 50public class SmsDispatchersController extends Handler { 51 private static final String TAG = "SmsDispatchersController"; 52 53 /** Radio is ON */ 54 private static final int EVENT_RADIO_ON = 11; 55 56 /** IMS registration/SMS format changed */ 57 private static final int EVENT_IMS_STATE_CHANGED = 12; 58 59 /** Callback from RIL_REQUEST_IMS_REGISTRATION_STATE */ 60 private static final int EVENT_IMS_STATE_DONE = 13; 61 62 private SMSDispatcher mCdmaDispatcher; 63 private SMSDispatcher mGsmDispatcher; 64 private ImsSmsDispatcher mImsSmsDispatcher; 65 66 private GsmInboundSmsHandler mGsmInboundSmsHandler; 67 private CdmaInboundSmsHandler mCdmaInboundSmsHandler; 68 69 private Phone mPhone; 70 /** Outgoing message counter. Shared by all dispatchers. */ 71 private final SmsUsageMonitor mUsageMonitor; 72 private final CommandsInterface mCi; 73 private final Context mContext; 74 75 /** true if IMS is registered and sms is supported, false otherwise.*/ 76 private boolean mIms = false; 77 private String mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN; 78 79 public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, 80 SmsUsageMonitor usageMonitor) { 81 Rlog.d(TAG, "SmsDispatchersController created"); 82 83 mContext = phone.getContext(); 84 mUsageMonitor = usageMonitor; 85 mCi = phone.mCi; 86 mPhone = phone; 87 88 // Create dispatchers, inbound SMS handlers and 89 // broadcast undelivered messages in raw table. 90 mImsSmsDispatcher = new ImsSmsDispatcher(phone, this); 91 mCdmaDispatcher = new CdmaSMSDispatcher(phone, this); 92 mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), 93 storageMonitor, phone); 94 mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), 95 storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher); 96 mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler); 97 SmsBroadcastUndelivered.initialize(phone.getContext(), 98 mGsmInboundSmsHandler, mCdmaInboundSmsHandler); 99 InboundSmsHandler.registerNewMessageNotificationActionHandler(phone.getContext()); 100 101 mCi.registerForOn(this, EVENT_RADIO_ON, null); 102 mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null); 103 } 104 105 /* Updates the phone object when there is a change */ 106 protected void updatePhoneObject(Phone phone) { 107 Rlog.d(TAG, "In IMS updatePhoneObject "); 108 mCdmaDispatcher.updatePhoneObject(phone); 109 mGsmDispatcher.updatePhoneObject(phone); 110 mGsmInboundSmsHandler.updatePhoneObject(phone); 111 mCdmaInboundSmsHandler.updatePhoneObject(phone); 112 } 113 114 public void dispose() { 115 mCi.unregisterForOn(this); 116 mCi.unregisterForImsNetworkStateChanged(this); 117 mGsmDispatcher.dispose(); 118 mCdmaDispatcher.dispose(); 119 mGsmInboundSmsHandler.dispose(); 120 mCdmaInboundSmsHandler.dispose(); 121 } 122 123 /** 124 * Handles events coming from the phone stack. Overridden from handler. 125 * 126 * @param msg the message to handle 127 */ 128 @Override 129 public void handleMessage(Message msg) { 130 AsyncResult ar; 131 132 switch (msg.what) { 133 case EVENT_RADIO_ON: 134 case EVENT_IMS_STATE_CHANGED: // received unsol 135 mCi.getImsRegistrationState(this.obtainMessage(EVENT_IMS_STATE_DONE)); 136 break; 137 138 case EVENT_IMS_STATE_DONE: 139 ar = (AsyncResult) msg.obj; 140 141 if (ar.exception == null) { 142 updateImsInfo(ar); 143 } else { 144 Rlog.e(TAG, "IMS State query failed with exp " 145 + ar.exception); 146 } 147 break; 148 149 default: 150 if (isCdmaMo()) { 151 mCdmaDispatcher.handleMessage(msg); 152 } else { 153 mGsmDispatcher.handleMessage(msg); 154 } 155 } 156 } 157 158 private void setImsSmsFormat(int format) { 159 switch (format) { 160 case PhoneConstants.PHONE_TYPE_GSM: 161 mImsSmsFormat = SmsConstants.FORMAT_3GPP; 162 break; 163 case PhoneConstants.PHONE_TYPE_CDMA: 164 mImsSmsFormat = SmsConstants.FORMAT_3GPP2; 165 break; 166 default: 167 mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN; 168 break; 169 } 170 } 171 172 private void updateImsInfo(AsyncResult ar) { 173 int[] responseArray = (int[]) ar.result; 174 setImsSmsFormat(responseArray[1]); 175 mIms = responseArray[0] == 1 && !SmsConstants.FORMAT_UNKNOWN.equals(mImsSmsFormat); 176 Rlog.d(TAG, "IMS registration state: " + mIms + " format: " + mImsSmsFormat); 177 } 178 179 /** 180 * Inject an SMS PDU into the android platform only if it is class 1. 181 * 182 * @param pdu is the byte array of pdu to be injected into android telephony layer 183 * @param format is the format of SMS pdu (3gpp or 3gpp2) 184 * @param callback if not NULL this callback is triggered when the message is successfully 185 * received by the android telephony layer. This callback is triggered at 186 * the same time an SMS received from radio is responded back. 187 */ 188 @VisibleForTesting 189 public void injectSmsPdu(byte[] pdu, String format, SmsInjectionCallback callback) { 190 // TODO We need to decide whether we should allow injecting GSM(3gpp) 191 // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa. 192 android.telephony.SmsMessage msg = 193 android.telephony.SmsMessage.createFromPdu(pdu, format); 194 injectSmsPdu(msg, format, callback, false /* ignoreClass */); 195 } 196 197 /** 198 * Inject an SMS PDU into the android platform. 199 * 200 * @param msg is the {@link SmsMessage} to be injected into android telephony layer 201 * @param format is the format of SMS pdu (3gpp or 3gpp2) 202 * @param callback if not NULL this callback is triggered when the message is successfully 203 * received by the android telephony layer. This callback is triggered at 204 * the same time an SMS received from radio is responded back. 205 * @param ignoreClass if set to false, this method will inject class 1 sms only. 206 */ 207 @VisibleForTesting 208 public void injectSmsPdu(SmsMessage msg, String format, SmsInjectionCallback callback, 209 boolean ignoreClass) { 210 Rlog.d(TAG, "SmsDispatchersController:injectSmsPdu"); 211 try { 212 if (msg == null) { 213 Rlog.e(TAG, "injectSmsPdu: createFromPdu returned null"); 214 callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); 215 return; 216 } 217 218 if (!ignoreClass 219 && msg.getMessageClass() != android.telephony.SmsMessage.MessageClass.CLASS_1) { 220 Rlog.e(TAG, "injectSmsPdu: not class 1"); 221 callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); 222 return; 223 } 224 225 AsyncResult ar = new AsyncResult(callback, msg, null); 226 227 if (format.equals(SmsConstants.FORMAT_3GPP)) { 228 Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg 229 + ", format=" + format + "to mGsmInboundSmsHandler"); 230 mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar); 231 } else if (format.equals(SmsConstants.FORMAT_3GPP2)) { 232 Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg 233 + ", format=" + format + "to mCdmaInboundSmsHandler"); 234 mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar); 235 } else { 236 // Invalid pdu format. 237 Rlog.e(TAG, "Invalid pdu format: " + format); 238 callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); 239 } 240 } catch (Exception e) { 241 Rlog.e(TAG, "injectSmsPdu failed: ", e); 242 callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); 243 } 244 } 245 246 /** 247 * Retry the message along to the radio. 248 * 249 * @param tracker holds the SMS message to send 250 */ 251 public void sendRetrySms(SMSDispatcher.SmsTracker tracker) { 252 String oldFormat = tracker.mFormat; 253 254 // newFormat will be based on voice technology 255 String newFormat = 256 (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()) 257 ? mCdmaDispatcher.getFormat() : mGsmDispatcher.getFormat(); 258 259 // was previously sent sms format match with voice tech? 260 if (oldFormat.equals(newFormat)) { 261 if (isCdmaFormat(newFormat)) { 262 Rlog.d(TAG, "old format matched new format (cdma)"); 263 mCdmaDispatcher.sendSms(tracker); 264 return; 265 } else { 266 Rlog.d(TAG, "old format matched new format (gsm)"); 267 mGsmDispatcher.sendSms(tracker); 268 return; 269 } 270 } 271 272 // format didn't match, need to re-encode. 273 HashMap map = tracker.getData(); 274 275 // to re-encode, fields needed are: scAddr, destAddr, and 276 // text if originally sent as sendText or 277 // data and destPort if originally sent as sendData. 278 if (!(map.containsKey("scAddr") && map.containsKey("destAddr") 279 && (map.containsKey("text") 280 || (map.containsKey("data") && map.containsKey("destPort"))))) { 281 // should never come here... 282 Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!"); 283 tracker.onFailed(mContext, SmsManager.RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/); 284 return; 285 } 286 String scAddr = (String) map.get("scAddr"); 287 String destAddr = (String) map.get("destAddr"); 288 289 SmsMessageBase.SubmitPduBase pdu = null; 290 // figure out from tracker if this was sendText/Data 291 if (map.containsKey("text")) { 292 Rlog.d(TAG, "sms failed was text"); 293 String text = (String) map.get("text"); 294 295 if (isCdmaFormat(newFormat)) { 296 Rlog.d(TAG, "old format (gsm) ==> new format (cdma)"); 297 pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( 298 scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null); 299 } else { 300 Rlog.d(TAG, "old format (cdma) ==> new format (gsm)"); 301 pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu( 302 scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null); 303 } 304 } else if (map.containsKey("data")) { 305 Rlog.d(TAG, "sms failed was data"); 306 byte[] data = (byte[]) map.get("data"); 307 Integer destPort = (Integer) map.get("destPort"); 308 309 if (isCdmaFormat(newFormat)) { 310 Rlog.d(TAG, "old format (gsm) ==> new format (cdma)"); 311 pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( 312 scAddr, destAddr, destPort.intValue(), data, 313 (tracker.mDeliveryIntent != null)); 314 } else { 315 Rlog.d(TAG, "old format (cdma) ==> new format (gsm)"); 316 pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu( 317 scAddr, destAddr, destPort.intValue(), data, 318 (tracker.mDeliveryIntent != null)); 319 } 320 } 321 322 // replace old smsc and pdu with newly encoded ones 323 map.put("smsc", pdu.encodedScAddress); 324 map.put("pdu", pdu.encodedMessage); 325 326 SMSDispatcher dispatcher = (isCdmaFormat(newFormat)) ? mCdmaDispatcher : mGsmDispatcher; 327 328 tracker.mFormat = dispatcher.getFormat(); 329 dispatcher.sendSms(tracker); 330 } 331 332 public boolean isIms() { 333 return mIms; 334 } 335 336 public String getImsSmsFormat() { 337 return mImsSmsFormat; 338 } 339 340 /** 341 * Determines whether or not to use CDMA format for MO SMS. 342 * If SMS over IMS is supported, then format is based on IMS SMS format, 343 * otherwise format is based on current phone type. 344 * 345 * @return true if Cdma format should be used for MO SMS, false otherwise. 346 */ 347 protected boolean isCdmaMo() { 348 if (!isIms()) { 349 // IMS is not registered, use Voice technology to determine SMS format. 350 return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()); 351 } 352 // IMS is registered with SMS support 353 return isCdmaFormat(mImsSmsFormat); 354 } 355 356 /** 357 * Determines whether or not format given is CDMA format. 358 * 359 * @param format 360 * @return true if format given is CDMA format, false otherwise. 361 */ 362 public boolean isCdmaFormat(String format) { 363 return (mCdmaDispatcher.getFormat().equals(format)); 364 } 365 366 /** 367 * Send a data based SMS to a specific application port. 368 * 369 * @param destAddr the address to send the message to 370 * @param scAddr is the service center address or null to use 371 * the current default SMSC 372 * @param destPort the port to deliver the message to 373 * @param data the body of the message to send 374 * @param sentIntent if not NULL this <code>PendingIntent</code> is 375 * broadcast when the message is successfully sent, or failed. 376 * The result code will be <code>Activity.RESULT_OK<code> for success, 377 * or one of these errors:<br> 378 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> 379 * <code>RESULT_ERROR_RADIO_OFF</code><br> 380 * <code>RESULT_ERROR_NULL_PDU</code><br> 381 * <code>RESULT_ERROR_NO_SERVICE</code><br>. 382 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include 383 * the extra "errorCode" containing a radio technology specific value, 384 * generally only useful for troubleshooting.<br> 385 * The per-application based SMS control checks sentIntent. If sentIntent 386 * is NULL the caller will be checked against all unknown applications, 387 * which cause smaller number of SMS to be sent in checking period. 388 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is 389 * broadcast when the message is delivered to the recipient. The 390 * raw pdu of the status report is in the extended data ("pdu"). 391 */ 392 protected void sendData(String destAddr, String scAddr, int destPort, 393 byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { 394 if (mImsSmsDispatcher.isAvailable()) { 395 mImsSmsDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, 396 deliveryIntent); 397 } else if (isCdmaMo()) { 398 mCdmaDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent); 399 } else { 400 mGsmDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent); 401 } 402 } 403 404 /** 405 * Send a text based SMS. 406 * @param destAddr the address to send the message to 407 * @param scAddr is the service center address or null to use 408 * the current default SMSC 409 * @param text the body of the message to send 410 * @param sentIntent if not NULL this <code>PendingIntent</code> is 411 * broadcast when the message is successfully sent, or failed. 412 * The result code will be <code>Activity.RESULT_OK<code> for success, 413 * or one of these errors:<br> 414 * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> 415 * <code>RESULT_ERROR_RADIO_OFF</code><br> 416 * <code>RESULT_ERROR_NULL_PDU</code><br> 417 * <code>RESULT_ERROR_NO_SERVICE</code><br>. 418 * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include 419 * the extra "errorCode" containing a radio technology specific value, 420 * generally only useful for troubleshooting.<br> 421 * The per-application based SMS control checks sentIntent. If sentIntent 422 * is NULL the caller will be checked against all unknown applications, 423 * which cause smaller number of SMS to be sent in checking period. 424 * @param deliveryIntent if not NULL this <code>PendingIntent</code> is 425 * broadcast when the message is delivered to the recipient. The 426 * @param messageUri optional URI of the message if it is already stored in the system 427 * @param callingPkg the calling package name 428 * @param persistMessage whether to save the sent message into SMS DB for a 429 * non-default SMS app. 430 * @param priority Priority level of the message 431 * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 432 * --------------------------------- 433 * PRIORITY | Level of Priority 434 * --------------------------------- 435 * '00' | Normal 436 * '01' | Interactive 437 * '10' | Urgent 438 * '11' | Emergency 439 * ---------------------------------- 440 * Any Other values included Negative considered as Invalid Priority Indicator of the message. 441 * @param expectMore is a boolean to indicate the sending messages through same link or not. 442 * @param validityPeriod Validity Period of the message in mins. 443 * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. 444 * Validity Period(Minimum) -> 5 mins 445 * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). 446 * Any Other values included Negative considered as Invalid Validity Period of the message. 447 */ 448 public void sendText(String destAddr, String scAddr, String text, 449 PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri, 450 String callingPkg, boolean persistMessage, int priority, 451 boolean expectMore, int validityPeriod) { 452 if (mImsSmsDispatcher.isAvailable()) { 453 mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, 454 messageUri, callingPkg, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, 455 false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED); 456 } else { 457 if (isCdmaMo()) { 458 mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, 459 messageUri, callingPkg, persistMessage, priority, expectMore, 460 validityPeriod); 461 } else { 462 mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, 463 messageUri, callingPkg, persistMessage, priority, expectMore, 464 validityPeriod); 465 } 466 } 467 } 468 469 /** 470 * Send a multi-part text based SMS. 471 * @param destAddr the address to send the message to 472 * @param scAddr is the service center address or null to use 473 * the current default SMSC 474 * @param parts an <code>ArrayList</code> of strings that, in order, 475 * comprise the original message 476 * @param sentIntents if not null, an <code>ArrayList</code> of 477 * <code>PendingIntent</code>s (one for each message part) that is 478 * broadcast when the corresponding message part has been sent. 479 * The result code will be <code>Activity.RESULT_OK<code> for success, 480 * or one of these errors: 481 * <code>RESULT_ERROR_GENERIC_FAILURE</code> 482 * <code>RESULT_ERROR_RADIO_OFF</code> 483 * <code>RESULT_ERROR_NULL_PDU</code> 484 * <code>RESULT_ERROR_NO_SERVICE</code>. 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 deliveryIntents if not null, an <code>ArrayList</code> of 489 * <code>PendingIntent</code>s (one for each message part) that is 490 * broadcast when the corresponding message part has been delivered 491 * to the recipient. The raw pdu of the status report is in the 492 * @param messageUri optional URI of the message if it is already stored in the system 493 * @param callingPkg the calling package name 494 * @param persistMessage whether to save the sent message into SMS DB for a 495 * non-default SMS app. 496 * @param priority Priority level of the message 497 * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 498 * --------------------------------- 499 * PRIORITY | Level of Priority 500 * --------------------------------- 501 * '00' | Normal 502 * '01' | Interactive 503 * '10' | Urgent 504 * '11' | Emergency 505 * ---------------------------------- 506 * Any Other values included Negative considered as Invalid Priority Indicator of the message. 507 * @param expectMore is a boolean to indicate the sending messages through same link or not. 508 * @param validityPeriod Validity Period of the message in mins. 509 * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. 510 * Validity Period(Minimum) -> 5 mins 511 * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). 512 * Any Other values included Negative considered as Invalid Validity Period of the message. 513 514 */ 515 protected void sendMultipartText(String destAddr, String scAddr, 516 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, 517 ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg, 518 boolean persistMessage, int priority, boolean expectMore, int validityPeriod) { 519 if (mImsSmsDispatcher.isAvailable()) { 520 mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, 521 deliveryIntents, messageUri, callingPkg, persistMessage, 522 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, 523 false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED); 524 } else { 525 if (isCdmaMo()) { 526 mCdmaDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, 527 deliveryIntents, messageUri, callingPkg, persistMessage, priority, 528 expectMore, validityPeriod); 529 } else { 530 mGsmDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, 531 deliveryIntents, messageUri, callingPkg, persistMessage, priority, 532 expectMore, validityPeriod); 533 } 534 } 535 } 536 537 /** 538 * Returns the premium SMS permission for the specified package. If the package has never 539 * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER} 540 * will be returned. 541 * @param packageName the name of the package to query permission 542 * @return one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN}, 543 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, 544 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or 545 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} 546 */ 547 public int getPremiumSmsPermission(String packageName) { 548 return mUsageMonitor.getPremiumSmsPermission(packageName); 549 } 550 551 /** 552 * Sets the premium SMS permission for the specified package and save the value asynchronously 553 * to persistent storage. 554 * @param packageName the name of the package to set permission 555 * @param permission one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, 556 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or 557 * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} 558 */ 559 public void setPremiumSmsPermission(String packageName, int permission) { 560 mUsageMonitor.setPremiumSmsPermission(packageName, permission); 561 } 562 563 public SmsUsageMonitor getUsageMonitor() { 564 return mUsageMonitor; 565 } 566 567 /** 568 * Triggers the correct method for handling the sms status report based on the format. 569 * 570 * @param tracker the sms tracker. 571 * @param format the format. 572 * @param pdu the pdu of the report. 573 * @return a Pair in which the first boolean is whether the report was handled successfully 574 * or not and the second boolean is whether processing the sms is complete and the 575 * tracker no longer need to be kept track of, false if we should expect more callbacks 576 * and the tracker should be kept. 577 */ 578 public Pair<Boolean, Boolean> handleSmsStatusReport(SMSDispatcher.SmsTracker tracker, 579 String format, byte[] pdu) { 580 if (isCdmaFormat(format)) { 581 return handleCdmaStatusReport(tracker, format, pdu); 582 } else { 583 return handleGsmStatusReport(tracker, format, pdu); 584 } 585 } 586 587 private Pair<Boolean, Boolean> handleCdmaStatusReport(SMSDispatcher.SmsTracker tracker, 588 String format, byte[] pdu) { 589 tracker.updateSentMessageStatus(mContext, Sms.STATUS_COMPLETE); 590 boolean success = triggerDeliveryIntent(tracker, format, pdu); 591 return new Pair(success, true /* complete */); 592 } 593 594 private Pair<Boolean, Boolean> handleGsmStatusReport(SMSDispatcher.SmsTracker tracker, 595 String format, byte[] pdu) { 596 com.android.internal.telephony.gsm.SmsMessage sms = 597 com.android.internal.telephony.gsm.SmsMessage.newFromCDS(pdu); 598 boolean complete = false; 599 boolean success = false; 600 if (sms != null) { 601 int tpStatus = sms.getStatus(); 602 if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) { 603 // Update the message status (COMPLETE or FAILED) 604 tracker.updateSentMessageStatus(mContext, tpStatus); 605 complete = true; 606 } 607 success = triggerDeliveryIntent(tracker, format, pdu); 608 } 609 return new Pair(success, complete); 610 } 611 612 private boolean triggerDeliveryIntent(SMSDispatcher.SmsTracker tracker, String format, 613 byte[] pdu) { 614 PendingIntent intent = tracker.mDeliveryIntent; 615 Intent fillIn = new Intent(); 616 fillIn.putExtra("pdu", pdu); 617 fillIn.putExtra("format", format); 618 try { 619 intent.send(mContext, Activity.RESULT_OK, fillIn); 620 return true; 621 } catch (CanceledException ex) { 622 return false; 623 } 624 } 625 626 627 public interface SmsInjectionCallback { 628 void onSmsInjectedResult(int result); 629 } 630} 631