diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | telephony/java/android/telephony/SmsManager.java | 894 | ||||
| -rw-r--r-- | telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl | 23 | ||||
| -rw-r--r-- | telephony/java/com/android/internal/telephony/ITelephony.aidl | 8 |
4 files changed, 823 insertions, 103 deletions
diff --git a/Android.bp b/Android.bp index 7dcafb6f78b6..78509ed5d203 100644 --- a/Android.bp +++ b/Android.bp @@ -549,6 +549,7 @@ java_defaults { "telephony/java/com/android/ims/ImsConfigListener.aidl", "telephony/java/com/android/internal/telephony/IApnSourceService.aidl", "telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl", + "telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl", "telephony/java/com/android/internal/telephony/IMms.aidl", "telephony/java/com/android/internal/telephony/INumberVerificationCallback.aidl", "telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl", diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 4728039faa14..f9e7fec721ee 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -24,17 +24,23 @@ import android.app.ActivityThread; import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.BaseBundle; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.provider.Telephony; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.Log; +import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.IMms; import com.android.internal.telephony.ISms; +import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.SmsRawData; import java.util.ArrayList; @@ -107,7 +113,7 @@ public final class SmsManager { * Whether MMS is enabled for the current carrier (boolean type) */ public static final String - MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL; + MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL; /** * Whether group MMS is enabled for the current carrier (boolean type) */ @@ -275,12 +281,6 @@ public final class SmsManager { public static final String MMS_CONFIG_CLOSE_CONNECTION = CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL; - /* - * Forwarded constants from SimDialogActivity. - */ - private static String DIALOG_TYPE_KEY = "dialog_type"; - private static final int SMS_PICK = 2; - /** * 3gpp2 SMS priority is not specified * @hide @@ -293,6 +293,18 @@ public final class SmsManager { public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1; /** + * Extra key passed into a PendingIntent when the SMS operation failed due to there being no + * default set. + */ + private static final String NO_DEFAULT_EXTRA = "noDefault"; + + // result of asking the user for a subscription to perform an operation. + private interface SubscriptionResolverResult { + void onSuccess(int subId); + void onFailure(); + } + + /** * Send a text based SMS. * * <p class="note"><strong>Note:</strong> Using this method requires that your app has the @@ -304,6 +316,15 @@ public final class SmsManager { * responsible for writing its sent messages to the SMS Provider). For information about * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p> * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use @@ -347,15 +368,51 @@ public final class SmsManager { throw new IllegalArgumentException("Invalid message body"); } - try { - // If the subscription is invalid or default, we will use the default phone to send the - // SMS and possibly fail later in the SMS sending process. + final Context context = ActivityThread.currentApplication().getApplicationContext(); + // We will only show the SMS disambiguation dialog in the case that the message is being + // persisted. This is for two reasons: + // 1) Messages that are not persisted are sent by carrier/OEM apps for a specific + // subscription and require special permissions. These messages are usually not sent by + // the device user and should not have an SMS disambiguation dialog associated with them + // because the device user did not trigger them. + // 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the SEND_SMS + // permission. If we call resolveSubscriptionForOperation from a carrier/OEM app that has + // the correct MODIFY_PHONE_STATE or carrier permissions, but no SEND_SMS, it will throw + // an incorrect SecurityException. + if (persistMessage) { + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + ISms iSms = getISmsServiceOrThrow(); + try { + iSms.sendTextForSubscriber(subId, packageName, + destinationAddress, scAddress, text, sentIntent, deliveryIntent, + persistMessage); + } catch (RemoteException e) { + Log.e(TAG, "sendTextMessageInternal: Couldn't send SMS, exception - " + + e.getMessage()); + notifySmsGenericError(sentIntent); + } + } + + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntent); + } + }); + } else { + // Not persisting the message, used by sendTextMessageWithoutPersisting() and is not + // visible to the user. ISms iSms = getISmsServiceOrThrow(); - iSms.sendTextForSubscriber(getSubscriptionId(), packageName, - destinationAddress, scAddress, text, sentIntent, deliveryIntent, - persistMessage); - } catch (RemoteException ex) { - // ignore it + try { + iSms.sendTextForSubscriber(getSubscriptionId(), packageName, + destinationAddress, scAddress, text, sentIntent, deliveryIntent, + persistMessage); + } catch (RemoteException e) { + Log.e(TAG, "sendTextMessageInternal (no persist): Couldn't send SMS, exception - " + + e.getMessage()); + notifySmsGenericError(sentIntent); + } } } @@ -372,6 +429,17 @@ public final class SmsManager { * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), or that the calling app is * the default IMS app (see * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}). + * </p> + * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the SMS being sent on the subscription associated with logical + * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the + * correct subscription. + * </p> * * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent) */ @@ -391,6 +459,16 @@ public final class SmsManager { * A variant of {@link SmsManager#sendTextMessage} that allows self to be the caller. This is * for internal use only. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the SMS being sent on the subscription associated with logical + * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the + * correct subscription. + * </p> + * * @param persistMessage whether to persist the sent message in the SMS app. the caller must be * the Phone process if set to false. * @@ -414,13 +492,22 @@ public final class SmsManager { destinationAddress, scAddress, text, sentIntent, deliveryIntent, persistMessage); } catch (RemoteException ex) { - // ignore it + notifySmsGenericError(sentIntent); } } /** * Send a text based SMS with messaging options. * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use * the current default SMSC @@ -491,16 +578,59 @@ public final class SmsManager { validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; } - try { - ISms iSms = getISmsServiceOrThrow(); - if (iSms != null) { - iSms.sendTextForSubscriberWithOptions(getSubscriptionId(), - ActivityThread.currentPackageName(), destinationAddress, scAddress, text, - sentIntent, deliveryIntent, persistMessage, priority, expectMore, - validityPeriod); + final int finalPriority = priority; + final int finalValidity = validityPeriod; + final Context context = ActivityThread.currentApplication().getApplicationContext(); + // We will only show the SMS disambiguation dialog in the case that the message is being + // persisted. This is for two reasons: + // 1) Messages that are not persisted are sent by carrier/OEM apps for a specific + // subscription and require special permissions. These messages are usually not sent by + // the device user and should not have an SMS disambiguation dialog associated with them + // because the device user did not trigger them. + // 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the SEND_SMS + // permission. If we call resolveSubscriptionForOperation from a carrier/OEM app that has + // the correct MODIFY_PHONE_STATE or carrier permissions, but no SEND_SMS, it will throw + // an incorrect SecurityException. + if (persistMessage) { + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + try { + ISms iSms = getISmsServiceOrThrow(); + if (iSms != null) { + iSms.sendTextForSubscriberWithOptions(subId, + ActivityThread.currentPackageName(), destinationAddress, + scAddress, + text, sentIntent, deliveryIntent, persistMessage, finalPriority, + expectMore, finalValidity); + } + } catch (RemoteException e) { + Log.e(TAG, "sendTextMessageInternal: Couldn't send SMS, exception - " + + e.getMessage()); + notifySmsGenericError(sentIntent); + } + } + + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntent); + } + }); + } else { + try { + ISms iSms = getISmsServiceOrThrow(); + if (iSms != null) { + iSms.sendTextForSubscriberWithOptions(getSubscriptionId(), + ActivityThread.currentPackageName(), destinationAddress, + scAddress, + text, sentIntent, deliveryIntent, persistMessage, finalPriority, + expectMore, finalValidity); + } + } catch (RemoteException e) { + Log.e(TAG, "sendTextMessageInternal(no persist): Couldn't send SMS, exception - " + + e.getMessage()); + notifySmsGenericError(sentIntent); } - } catch (RemoteException ex) { - // ignore it } } @@ -512,6 +642,16 @@ public final class SmsManager { * privileges. * </p> * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the SMS being sent on the subscription associated with logical + * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the + * correct subscription. + * </p> + * * @see #sendTextMessage(String, String, String, PendingIntent, * PendingIntent, int, boolean, int) * @hide @@ -532,6 +672,16 @@ public final class SmsManager { * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the SMS being injected on the subscription associated with + * logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is + * delivered to the correct subscription. + * </p> + * * @param pdu is the byte array of pdu to be injected into android application framework * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or * {@link SmsMessage#FORMAT_3GPP2}) @@ -559,7 +709,13 @@ public final class SmsManager { getSubscriptionId(), pdu, format, receivedIntent); } } catch (RemoteException ex) { - // ignore it + try { + if (receivedIntent != null) { + receivedIntent.send(Telephony.Sms.Intents.RESULT_SMS_GENERIC_ERROR); + } + } catch (PendingIntent.CanceledException cx) { + // Don't worry about it, we do not need to notify the caller if this is the case. + } } } @@ -591,6 +747,16 @@ public final class SmsManager { * responsible for writing its sent messages to the SMS Provider). For information about * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p> * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * + * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use * the current default SMSC @@ -626,11 +792,22 @@ public final class SmsManager { } /** - * @hide * Similar method as #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList) - * With an additional argument - * @param packageName serves as the default package name if ActivityThread.currentpackageName is - * null. + * With an additional argument. + * + * <p class="note"><strong>Note:</strong> This method is intended for internal use the Telephony + * framework and will never trigger an SMS disambiguation dialog. If this method is called on a + * device that has multiple active subscriptions, this {@link SmsManager} instance has been + * created with {@link #getDefault()}, and no user-defined default subscription is defined, the + * subscription ID associated with this message will be INVALID, which will result in the SMS + * being sent on the subscription associated with logical slot 0. Use + * {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the correct + * subscription. + * </p> + * + * @param packageName serves as the default package name if + * {@link ActivityThread#currentPackageName()} is null. + * @hide */ public void sendMultipartTextMessageExternal( String destinationAddress, String scAddress, ArrayList<String> parts, @@ -654,13 +831,52 @@ public final class SmsManager { } if (parts.size() > 1) { - try { - ISms iSms = getISmsServiceOrThrow(); - iSms.sendMultipartTextForSubscriber(getSubscriptionId(), - packageName, destinationAddress, scAddress, parts, - sentIntents, deliveryIntents, persistMessage); - } catch (RemoteException ex) { - // ignore it + final Context context = ActivityThread.currentApplication().getApplicationContext(); + // We will only show the SMS disambiguation dialog in the case that the message is being + // persisted. This is for two reasons: + // 1) Messages that are not persisted are sent by carrier/OEM apps for a specific + // subscription and require special permissions. These messages are usually not sent + // by the device user and should not have an SMS disambiguation dialog associated + // with them because the device user did not trigger them. + // 2) The SMS disambiguation dialog ONLY checks to make sure that the user has the + // SEND_SMS permission. If we call resolveSubscriptionForOperation from a carrier/OEM + // app that has the correct MODIFY_PHONE_STATE or carrier permissions, but no + // SEND_SMS, it will throw an incorrect SecurityException. + if (persistMessage) { + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + try { + ISms iSms = getISmsServiceOrThrow(); + iSms.sendMultipartTextForSubscriber(subId, packageName, + destinationAddress, scAddress, parts, sentIntents, + deliveryIntents, persistMessage); + } catch (RemoteException e) { + Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - " + + e.getMessage()); + notifySmsGenericError(sentIntents); + } + } + + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntents); + } + }); + } else { + // Called by apps that are not user facing, don't show disambiguation dialog. + try { + ISms iSms = getISmsServiceOrThrow(); + if (iSms != null) { + iSms.sendMultipartTextForSubscriber(getSubscriptionId(), packageName, + destinationAddress, scAddress, parts, sentIntents, deliveryIntents, + persistMessage); + } + } catch (RemoteException e) { + Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - " + + e.getMessage()); + notifySmsGenericError(sentIntents); + } } } else { PendingIntent sentIntent = null; @@ -679,6 +895,15 @@ public final class SmsManager { /** * Send a multi-part text based SMS without writing it into the SMS Provider. * + * <p> + * If this method is called on a device with multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the SMS sent on the subscription associated with slot + * 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent using the + * correct subscription. + * </p> + * * <p>Requires Permission: * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier * privileges. @@ -710,6 +935,15 @@ public final class SmsManager { * responsible for writing its sent messages to the SMS Provider). For information about * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p> * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use * the current default SMSC @@ -777,24 +1011,56 @@ public final class SmsManager { } if (priority < 0x00 || priority > 0x03) { - priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; + priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; } if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { - validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; + validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; } if (parts.size() > 1) { - try { - ISms iSms = getISmsServiceOrThrow(); - if (iSms != null) { - iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(), - ActivityThread.currentPackageName(), destinationAddress, scAddress, - parts, sentIntents, deliveryIntents, persistMessage, priority, - expectMore, validityPeriod); + final int finalPriority = priority; + final int finalValidity = validityPeriod; + final Context context = ActivityThread.currentApplication().getApplicationContext(); + if (persistMessage) { + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + try { + ISms iSms = getISmsServiceOrThrow(); + if (iSms != null) { + iSms.sendMultipartTextForSubscriberWithOptions(subId, + ActivityThread.currentPackageName(), destinationAddress, + scAddress, parts, sentIntents, deliveryIntents, + persistMessage, finalPriority, expectMore, finalValidity); + } + } catch (RemoteException e) { + Log.e(TAG, "sendMultipartTextMessageInternal: Couldn't send SMS - " + + e.getMessage()); + notifySmsGenericError(sentIntents); + } + } + + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntents); + } + }); + } else { + // Sent by apps that are not user visible, so don't show SIM disambiguation dialog. + try { + ISms iSms = getISmsServiceOrThrow(); + if (iSms != null) { + iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(), + ActivityThread.currentPackageName(), destinationAddress, + scAddress, parts, sentIntents, deliveryIntents, + persistMessage, finalPriority, expectMore, finalValidity); + } + } catch (RemoteException e) { + Log.e(TAG, "sendMultipartTextMessageInternal (no persist): Couldn't send SMS - " + + e.getMessage()); + notifySmsGenericError(sentIntents); } - } catch (RemoteException ex) { - // ignore it } } else { PendingIntent sentIntent = null; @@ -819,6 +1085,16 @@ public final class SmsManager { * privileges. * </p> * + * <p class="note"><strong>Note:</strong> This method is intended for internal use the Telephony + * framework and will never trigger an SMS disambiguation dialog. If this method is called on a + * device that has multiple active subscriptions, this {@link SmsManager} instance has been + * created with {@link #getDefault()}, and no user-defined default subscription is defined, the + * subscription ID associated with this message will be INVALID, which will result in the SMS + * being sent on the subscription associated with logical slot 0. Use + * {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the correct + * subscription. + * </p> + * * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, * ArrayList, int, boolean, int) * @hide @@ -832,12 +1108,21 @@ public final class SmsManager { validityPeriod); } - /** + /** * Send a data based SMS to a specific application port. * * <p class="note"><strong>Note:</strong> Using this method requires that your app has the * {@link android.Manifest.permission#SEND_SMS} permission.</p> * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * * @param destinationAddress the address to send the message to * @param scAddress is the service center address or null to use * the current default SMSC @@ -873,20 +1158,41 @@ public final class SmsManager { throw new IllegalArgumentException("Invalid message data"); } - try { - ISms iSms = getISmsServiceOrThrow(); - iSms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), - destinationAddress, scAddress, destinationPort & 0xFFFF, - data, sentIntent, deliveryIntent); - } catch (RemoteException ex) { - // ignore it - } + final Context context = ActivityThread.currentApplication().getApplicationContext(); + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + try { + ISms iSms = getISmsServiceOrThrow(); + iSms.sendDataForSubscriber(subId, ActivityThread.currentPackageName(), + destinationAddress, scAddress, destinationPort & 0xFFFF, data, + sentIntent, deliveryIntent); + } catch (RemoteException e) { + Log.e(TAG, "sendDataMessage: Couldn't send SMS - Exception: " + e.getMessage()); + notifySmsGenericError(sentIntent); + } + } + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntent); + } + }); } /** * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is * for internal use only. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the SMS being sent on the subscription associated with logical + * slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the SMS is sent on the + * correct subscription. + * </p> + * * @hide */ public void sendDataMessageWithSelfPermissions( @@ -905,8 +1211,10 @@ public final class SmsManager { iSms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(), ActivityThread.currentPackageName(), destinationAddress, scAddress, destinationPort & 0xFFFF, data, sentIntent, deliveryIntent); - } catch (RemoteException ex) { - // ignore it + } catch (RemoteException e) { + Log.e(TAG, "sendDataMessageWithSelfPermissions: Couldn't send SMS - Exception: " + + e.getMessage()); + notifySmsGenericError(sentIntent); } } @@ -914,20 +1222,44 @@ public final class SmsManager { * Get the SmsManager associated with the default subscription id. The instance will always be * associated with the default subscription id, even if the default subscription id changes. * - * @return the SmsManager associated with the default subscription id + * <p class="note"><strong>Note:</strong> For devices that support multiple active subscriptions + * at a time, SmsManager will track the subscription set by the user as the default SMS + * subscription. If the user has not set a default, {@link SmsManager} may + * start an activity to kick off a subscription disambiguation dialog. Most operations will not + * complete until the user has chosen the subscription that will be associated with the + * operation. If the user cancels the dialog without choosing a subscription, one of the + * following will happen, depending on the target SDK version of the application. For + * compatibility purposes, if the target SDK level is <= 28, telephony will still send the SMS + * over the first available subscription. If the target SDK level is > 28, the operation will + * fail to complete. + * </p> + * + * <p class="note"><strong>Note:</strong> If this method is used to perform an operation on a + * device that has multiple active subscriptions, the user has not set a default SMS + * subscription, and the operation is being performed while the application is not in the + * foreground, the SMS disambiguation dialog will not be shown. The result of the operation will + * conclude as if the user cancelled the disambiguation dialog and the operation will finish as + * outlined above, depending on the target SDK version of the calling application. It is safer + * to use {@link #getSmsManagerForSubscriptionId(int)} if the application will perform the + * operation while in the background because this can cause unpredictable results, such as the + * operation being sent over the wrong subscription or failing completely, depending on the + * user's default SMS subscription setting. + * </p> + * + * @return the {@link SmsManager} associated with the default subscription id. + * + * @see SubscriptionManager#getDefaultSmsSubscriptionId() */ public static SmsManager getDefault() { return sInstance; } /** - * Get the the instance of the SmsManager associated with a particular subscription ID. - * - * Constructing an {@link SmsManager} in this manner will never cause an SMS disambiguation - * dialog to appear, unlike {@link #getDefault()}. + * Get the instance of the SmsManager associated with a particular subscription ID. * - * @param subId an SMS subscription ID, typically accessed using {@link SubscriptionManager} - * @return the instance of the SmsManager associated with subscription + * <p class="note"><strong>Note:</strong> Constructing an {@link SmsManager} in this manner will + * never cause an SMS disambiguation dialog to appear, unlike {@link #getDefault()}. + * </p> * * @see SubscriptionManager#getActiveSubscriptionInfoList() * @see SubscriptionManager#getDefaultSmsSubscriptionId() @@ -952,19 +1284,186 @@ public final class SmsManager { * then this method may return different values at different points in time (if the user * changes the default subscription id). * - * Note: This method used to display a disambiguation dialog to the user asking them to choose a - * default subscription to send SMS messages over if they haven't chosen yet. Starting in Q, we - * allow the user to choose "ask every time" as a valid option for multi-SIM devices, so no - * disambiguation dialog will be shown and we will return - * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * <p class="note"><strong>Note:</strong> This method used to display a disambiguation dialog to + * the user asking them to choose a default subscription to send SMS messages over if they + * haven't chosen yet. Starting in API level 29, we allow the user to not have a default set as + * a valid option for the default SMS subscription on multi-SIM devices. We no longer show the + * disambiguation dialog and return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if the + * device has multiple active subscriptions and no default is set. + * </p> * * @return associated subscription ID or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if - * the default subscription id cannot be determined or the device supports multiple active + * the default subscription id cannot be determined or the device has multiple active * subscriptions and and no default is set ("ask every time") by the user. */ public int getSubscriptionId() { - return (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) - ? getDefaultSmsSubscriptionId() : mSubId; + try { + return (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) + ? getISmsServiceOrThrow().getPreferredSmsSubscription() : mSubId; + } catch (RemoteException e) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + } + + /** + * Resolves the subscription id to use for the associated operation if + * {@link #getSubscriptionId()} returns {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * + * If app targets API level 28 or below and they are either sending the SMS from the background + * or the device has more than one active subscription available and no default is set, we will + * use the first logical slot to send the SMS and possibly fail later in the SMS sending + * process. + * + * Regardless of the API level, if the app is the foreground app, then we will show the SMS + * disambiguation dialog. If the app is in the background and tries to perform an operation, we + * will not show the disambiguation dialog. + * + * See {@link #getDefault()} for a detailed explanation of how this method operates. + * + * @param resolverResult The callback that will be called when the subscription is resolved or + * fails to be resolved. + */ + private void resolveSubscriptionForOperation(SubscriptionResolverResult resolverResult) { + int subId = getSubscriptionId(); + boolean isSmsSimPickActivityNeeded = false; + final Context context = ActivityThread.currentApplication().getApplicationContext(); + try { + ISms iSms = getISmsService(); + if (iSms != null) { + // Determines if the SMS SIM pick activity should be shown. This is only shown if: + // 1) The device has multiple active subscriptions and an SMS default subscription + // hasn't been set, and + // 2) SmsManager is being called from the foreground app. + // Android does not allow background activity starts, so we need to block this. + // if Q+, do not perform requested operation if these two operations are not set. If + // <P, perform these operations on phone 0 (for compatibility purposes, since we + // used to not wait for the result of this activity). + isSmsSimPickActivityNeeded = iSms.isSmsSimPickActivityNeeded(subId); + } + } catch (RemoteException ex) { + Log.e(TAG, "resolveSubscriptionForOperation", ex); + } + if (!isSmsSimPickActivityNeeded) { + sendResolverResult(resolverResult, subId, false /*pickActivityShown*/); + return; + } + // We need to ask the user pick an appropriate subid for the operation. + Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for package " + + context.getPackageName()); + try { + // Create the SMS pick activity and call back once the activity is complete. Can't do + // it here because we do not have access to the activity context that is performing this + // operation. + // Requires that the calling process has the SEND_SMS permission. + getITelephony().enqueueSmsPickResult(context.getOpPackageName(), + new IIntegerConsumer.Stub() { + @Override + public void accept(int subId) { + // Runs on binder thread attached to this app's process. + sendResolverResult(resolverResult, subId, true /*pickActivityShown*/); + } + }); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to launch activity", ex); + // pickActivityShown is true here because we want to call sendResolverResult and always + // have this operation fail. This is because we received a RemoteException here, which + // means that telephony is not available and the next operation to Telephony will fail + // as well anyways, so we might as well shortcut fail here first. + sendResolverResult(resolverResult, subId, true /*pickActivityShown*/); + } + } + + private void sendResolverResult(SubscriptionResolverResult resolverResult, int subId, + boolean pickActivityShown) { + if (SubscriptionManager.isValidSubscriptionId(subId)) { + resolverResult.onSuccess(subId); + return; + } + + if (getTargetSdkVersion() <= Build.VERSION_CODES.P && !pickActivityShown) { + // Do not fail, return a success with an INVALID subid for apps targeting P or below + // that tried to perform an operation and the SMS disambiguation dialog was never shown, + // as these applications may not have been written to handle the failure case properly. + // This will resolve to performing the operation on phone 0 in telephony. + resolverResult.onSuccess(subId); + } else { + // Fail if the app targets Q or above or it targets P and below and the disambiguation + // dialog was shown and the user clicked out of it. + resolverResult.onFailure(); + } + } + + private static int getTargetSdkVersion() { + final Context context = ActivityThread.currentApplication().getApplicationContext(); + int targetSdk; + try { + targetSdk = context.getPackageManager().getApplicationInfo( + context.getOpPackageName(), 0).targetSdkVersion; + } catch (PackageManager.NameNotFoundException e) { + // Default to old behavior if we can not find this. + targetSdk = -1; + } + return targetSdk; + } + + private static ITelephony getITelephony() { + ITelephony binder = ITelephony.Stub.asInterface( + ServiceManager.getService(Context.TELEPHONY_SERVICE)); + if (binder == null) { + throw new RuntimeException("Could not find Telephony Service."); + } + return binder; + } + + private static void notifySmsErrorNoDefaultSet(Context context, PendingIntent pendingIntent) { + if (pendingIntent != null) { + Intent errorMessage = new Intent(); + errorMessage.putExtra(NO_DEFAULT_EXTRA, true); + try { + pendingIntent.send(context, RESULT_ERROR_GENERIC_FAILURE, errorMessage); + } catch (PendingIntent.CanceledException e) { + // Don't worry about it, we do not need to notify the caller if this is the case. + } + } + } + + private static void notifySmsErrorNoDefaultSet(Context context, + List<PendingIntent> pendingIntents) { + if (pendingIntents != null) { + for (PendingIntent pendingIntent : pendingIntents) { + Intent errorMessage = new Intent(); + errorMessage.putExtra(NO_DEFAULT_EXTRA, true); + try { + pendingIntent.send(context, RESULT_ERROR_GENERIC_FAILURE, errorMessage); + } catch (PendingIntent.CanceledException e) { + // Don't worry about it, we do not need to notify the caller if this is the + // case. + } + } + } + } + + private static void notifySmsGenericError(PendingIntent pendingIntent) { + if (pendingIntent != null) { + try { + pendingIntent.send(RESULT_ERROR_GENERIC_FAILURE); + } catch (PendingIntent.CanceledException e) { + // Don't worry about it, we do not need to notify the caller if this is the case. + } + } + } + + private static void notifySmsGenericError(List<PendingIntent> pendingIntents) { + if (pendingIntents != null) { + for (PendingIntent pendingIntent : pendingIntents) { + try { + pendingIntent.send(RESULT_ERROR_GENERIC_FAILURE); + } catch (PendingIntent.CanceledException e) { + // Don't worry about it, we do not need to notify the caller if this is the + // case. + } + } + } } /** @@ -988,6 +1487,16 @@ public final class SmsManager { * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param smsc the SMSC for this message, or NULL for the default SMSC * @param pdu the raw PDU to store * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD, @@ -1023,6 +1532,16 @@ public final class SmsManager { * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param messageIndex is the record index of the message on ICC * @return true for success * @@ -1054,6 +1573,16 @@ public final class SmsManager { * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param messageIndex record index of message to update * @param newStatus new message status (STATUS_ON_ICC_READ, * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT, @@ -1086,6 +1615,16 @@ public final class SmsManager { * ICC (Integrated Circuit Card) is the card of the device. * For example, this can be the SIM or USIM for GSM. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @return <code>ArrayList</code> of <code>SmsMessage</code> objects * * {@hide} @@ -1118,6 +1657,16 @@ public final class SmsManager { * Note: This call is blocking, callers may want to avoid calling it from * the main thread of an application. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP) * or C.R1001-G (3GPP2) * @param ranType as defined in class SmsManager, the value can be one of these: @@ -1155,6 +1704,16 @@ public final class SmsManager { * Note: This call is blocking, callers may want to avoid calling it from * the main thread of an application. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP) * or C.R1001-G (3GPP2) * @param ranType as defined in class SmsManager, the value can be one of these: @@ -1194,6 +1753,16 @@ public final class SmsManager { * Note: This call is blocking, callers may want to avoid calling it from * the main thread of an application. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param startMessageId first message identifier as specified in TS 23.041 (3GPP) * or C.R1001-G (3GPP2) * @param endMessageId last message identifier as specified in TS 23.041 (3GPP) @@ -1238,6 +1807,16 @@ public final class SmsManager { * Note: This call is blocking, callers may want to avoid calling it from * the main thread of an application. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param startMessageId first message identifier as specified in TS 23.041 (3GPP) * or C.R1001-G (3GPP2) * @param endMessageId last message identifier as specified in TS 23.041 (3GPP) @@ -1278,6 +1857,16 @@ public final class SmsManager { * Create a list of <code>SmsMessage</code>s from a list of RawSmsData * records returned by <code>getAllMessagesFromIcc()</code> * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param records SMS EF records, returned by * <code>getAllMessagesFromIcc</code> * @return <code>ArrayList</code> of <code>SmsMessage</code> objects. @@ -1305,6 +1894,16 @@ public final class SmsManager { * SMS over IMS is supported if IMS is registered and SMS is supported * on IMS. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @return true if SMS over IMS is supported, false otherwise * * @see #getImsSmsFormat() @@ -1325,8 +1924,17 @@ public final class SmsManager { } /** - * Gets SMS format supported on IMS. SMS over IMS format is - * either 3GPP or 3GPP2. + * Gets SMS format supported on IMS. SMS over IMS format is either 3GPP or 3GPP2. + * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> * * @return SmsMessage.FORMAT_3GPP, * SmsMessage.FORMAT_3GPP2 @@ -1352,16 +1960,12 @@ public final class SmsManager { /** * Get default sms subscription id * - * @return the default SMS subscription id or + * @return the user-defined default SMS subscription id or * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if no default is set. */ public static int getDefaultSmsSubscriptionId() { - ISms iSms = null; try { - iSms = ISms.Stub.asInterface(ServiceManager.getService("isms")); - return iSms.getPreferredSmsSubscription(); - } catch (RemoteException ex) { - return -1; + return SubscriptionManager.getDefaultSmsSubscriptionId(); } catch (NullPointerException ex) { return -1; } @@ -1534,6 +2138,15 @@ public final class SmsManager { /** * Send an MMS message * + * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param context application context * @param contentUri the content Uri from which the message pdu will be read * @param locationUrl the optional location url where message should be sent to @@ -1564,6 +2177,15 @@ public final class SmsManager { /** * Download an MMS message from carrier by a given location URL * + * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param context application context * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained * from the MMS WAP push notification @@ -1587,9 +2209,8 @@ public final class SmsManager { if (iMms == null) { return; } - iMms.downloadMessage( - getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl, - contentUri, configOverrides, downloadedIntent); + iMms.downloadMessage(getSubscriptionId(), ActivityThread.currentPackageName(), + locationUrl, contentUri, configOverrides, downloadedIntent); } catch (RemoteException e) { // Ignore it } @@ -1827,6 +2448,15 @@ public final class SmsManager { * * You can only send a failed text message or a draft text message. * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * * @param messageUri the URI of the stored message * @param scAddress is the service center address or null to use the current default SMSC * @param sentIntent if not NULL this <code>PendingIntent</code> is @@ -1854,14 +2484,25 @@ public final class SmsManager { if (messageUri == null) { throw new IllegalArgumentException("Empty message URI"); } - try { - ISms iSms = getISmsServiceOrThrow(); - iSms.sendStoredText( - getSubscriptionId(), ActivityThread.currentPackageName(), messageUri, - scAddress, sentIntent, deliveryIntent); - } catch (RemoteException ex) { - // ignore it - } + final Context context = ActivityThread.currentApplication().getApplicationContext(); + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + try { + ISms iSms = getISmsServiceOrThrow(); + iSms.sendStoredText(subId, ActivityThread.currentPackageName(), messageUri, + scAddress, sentIntent, deliveryIntent); + } catch (RemoteException e) { + Log.e(TAG, "sendStoredTextMessage: Couldn't send SMS - Exception: " + + e.getMessage()); + notifySmsGenericError(sentIntent); + } + } + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntent); + } + }); } /** @@ -1871,6 +2512,15 @@ public final class SmsManager { * The provided <code>PendingIntent</code> lists should match the part number of the * divided text of the stored message by using <code>divideMessage</code> * + * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this + * manager on a multi-SIM device, this operation may fail sending the SMS message because no + * suitable default subscription could be found. In this case, if {@code sentIntent} is + * non-null, then the {@link PendingIntent} will be sent with an error code + * {@code RESULT_ERROR_GENERIC_FAILURE} and an extra string {@code "noDefault"} containing the + * boolean value {@code true}. See {@link #getDefault()} for more information on the conditions + * where this operation may fail. + * </p> + * * @param messageUri the URI of the stored message * @param scAddress is the service center address or null to use * the current default SMSC @@ -1902,14 +2552,25 @@ public final class SmsManager { if (messageUri == null) { throw new IllegalArgumentException("Empty message URI"); } - try { - ISms iSms = getISmsServiceOrThrow(); - iSms.sendStoredMultipartText( - getSubscriptionId(), ActivityThread.currentPackageName(), messageUri, - scAddress, sentIntents, deliveryIntents); - } catch (RemoteException ex) { - // ignore it - } + final Context context = ActivityThread.currentApplication().getApplicationContext(); + resolveSubscriptionForOperation(new SubscriptionResolverResult() { + @Override + public void onSuccess(int subId) { + try { + ISms iSms = getISmsServiceOrThrow(); + iSms.sendStoredMultipartText(subId, ActivityThread.currentPackageName(), + messageUri, scAddress, sentIntents, deliveryIntents); + } catch (RemoteException e) { + Log.e(TAG, "sendStoredTextMessage: Couldn't send SMS - Exception: " + + e.getMessage()); + notifySmsGenericError(sentIntents); + } + } + @Override + public void onFailure() { + notifySmsErrorNoDefaultSet(context, sentIntents); + } + }); } /** @@ -1918,6 +2579,15 @@ public final class SmsManager { * This is used for sending a previously sent, but failed-to-send, message or * for sending a text message that has been stored as a draft. * + * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @param messageUri the URI of the stored message * @param configOverrides the carrier-specific messaging configuration values to override for * sending the message. @@ -1991,6 +2661,16 @@ public final class SmsManager { /** * Get carrier-dependent configuration values. * + * <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier + * applications or the Telephony framework and will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @return bundle key/values pairs of configuration values */ public Bundle getCarrierConfigValues() { @@ -2006,7 +2686,7 @@ public final class SmsManager { } /** - * Create a single use app specific incoming SMS request for the the calling package. + * Create a single use app specific incoming SMS request for the calling package. * * This method returns a token that if included in a subsequent incoming SMS message will cause * {@code intent} to be sent with the SMS data. @@ -2017,6 +2697,15 @@ public final class SmsManager { * An app can only have one request at a time, if the app already has a request pending it will * be replaced with a new request. * + * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation + * dialog. If this method is called on a device that has multiple active subscriptions, this + * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined + * default subscription is defined, the subscription ID associated with this message will be + * INVALID, which will result in the operation being completed on the subscription associated + * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the + * operation is performed on the correct subscription. + * </p> + * * @return Token to include in an SMS message. The token will be 11 characters long. * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent */ @@ -2101,5 +2790,4 @@ public final class SmsManager { config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER)); return filtered; } - } diff --git a/telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl b/telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl new file mode 100644 index 000000000000..252460e56330 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/IIntegerConsumer.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +// Copies consumer pattern for an operation that requires an integer result from another process to +// finish. +oneway interface IIntegerConsumer { + void accept(int result); +}
\ No newline at end of file diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index bd26e1a5f2c7..1aba95bf5e5a 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.app.PendingIntent; import android.content.Intent; +import android.content.IntentSender; import android.os.Bundle; import android.os.IBinder; import android.os.Messenger; @@ -52,6 +53,7 @@ import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; +import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.INumberVerificationCallback; import com.android.internal.telephony.OperatorInfo; @@ -1975,6 +1977,12 @@ interface ITelephony { boolean isApnMetered(int apnType, int subId); /** + * Enqueue a pending sms Consumer, which will answer with the user specified selection for an + * outgoing SmsManager operation. + */ + oneway void enqueueSmsPickResult(String callingPackage, IIntegerConsumer subIdResult); + + /** * Returns the MMS user agent. */ String getMmsUserAgent(int subId); |