From 26b242f75e44253efd0d538b45095ec7bd1e5544 Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Tue, 30 Apr 2019 11:34:14 -0700 Subject: [PATCH 1/2] No longer show disambig when calling SmsManager#getSubId SmsManager#getSubId used to show a disambig dialog to pick a default SMS subscription when SmsManager#getSubscriptionId was called. Since it was used internally as well, it would cause many of these dialogs to pop up over and over again. Do not display the dialog anymore and instead move the dialog pop up to the methods that actually require the use of the dialog, such as sending a text. Bug: 130853716 Test: Manual, Default SMS app Merged-In: Ibbc07c961f1bdc19e1015885487e9f7b6a14fa83 Change-Id: I71ad38f76750c750b149e4cdcb6fcf99ed89212e --- .../java/android/telephony/SmsManager.java | 119 +++++++----------- 1 file changed, 43 insertions(+), 76 deletions(-) diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 0ee08e14324d3..4728039faa14a 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -22,10 +22,8 @@ import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.PendingIntent; -import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.os.BaseBundle; import android.os.Build; @@ -34,7 +32,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Log; import com.android.internal.telephony.IMms; import com.android.internal.telephony.ISms; @@ -54,22 +51,22 @@ import java.util.Map; /** * Manages SMS operations such as sending data, text, and pdu SMS messages. - * Get this object by calling the static method {@link #getDefault()}. + * Get this object by calling the static method {@link #getDefault()}. To create an instance of + * {@link SmsManager} associated with a specific subscription ID, call + * {@link #getSmsManagerForSubscriptionId(int)}. This is typically used for devices that support + * multiple active subscriptions at once. * *

For information about how to behave as the default SMS app on Android 4.4 (API level 19) * and higher, see {@link android.provider.Telephony}. + * + * @see SubscriptionManager#getActiveSubscriptionInfoList() */ public final class SmsManager { private static final String TAG = "SmsManager"; - /** - * A psuedo-subId that represents the default subId at any given time. The actual subId it - * represents changes as the default subId is changed. - */ - private static final int DEFAULT_SUBSCRIPTION_ID = -1002; - /** Singleton object constructed during class initialization. */ - private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID); + private static final SmsManager sInstance = new SmsManager( + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); private static final Object sLockObject = new Object(); /** @hide */ @@ -915,7 +912,7 @@ 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 is changed. + * associated with the default subscription id, even if the default subscription id changes. * * @return the SmsManager associated with the default subscription id */ @@ -924,14 +921,18 @@ public final class SmsManager { } /** - * Get the the instance of the SmsManager associated with a particular subscription id + * Get the the instance of the SmsManager associated with a particular subscription ID. * - * @param subId an SMS subscription id, typically accessed using - * {@link android.telephony.SubscriptionManager} - * @return the instance of the SmsManager associated with subId + * Constructing an {@link SmsManager} in this manner will never cause an SMS disambiguation + * dialog to appear, unlike {@link #getDefault()}. + * + * @param subId an SMS subscription ID, typically accessed using {@link SubscriptionManager} + * @return the instance of the SmsManager associated with subscription + * + * @see SubscriptionManager#getActiveSubscriptionInfoList() + * @see SubscriptionManager#getDefaultSmsSubscriptionId() */ public static SmsManager getSmsManagerForSubscriptionId(int subId) { - // TODO(shri): Add javadoc link once SubscriptionManager is made public api synchronized(sLockObject) { SmsManager smsManager = sSubInstances.get(subId); if (smsManager == null) { @@ -949,60 +950,21 @@ public final class SmsManager { /** * Get the associated subscription id. If the instance was returned by {@link #getDefault()}, * then this method may return different values at different points in time (if the user - * changes the default subscription id). It will return < 0 if the default subscription id - * cannot be determined. + * changes the default subscription id). * - * Additionally, to support legacy applications that are not multi-SIM aware, - * if the following are true: - * - We are using a multi-SIM device - * - A default SMS SIM has not been selected - * - At least one SIM subscription is available - * then ask the user to set the default SMS SIM. + * 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}. * - * @return associated subscription id + * @return associated subscription ID or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if + * the default subscription id cannot be determined or the device supports multiple active + * subscriptions and and no default is set ("ask every time") by the user. */ public int getSubscriptionId() { - final int subId = getSubIdOrDefault(); - boolean isSmsSimPickActivityNeeded = false; - final Context context = ActivityThread.currentApplication().getApplicationContext(); - try { - ISms iSms = getISmsService(); - if (iSms != null) { - isSmsSimPickActivityNeeded = iSms.isSmsSimPickActivityNeeded(subId); - } - } catch (RemoteException ex) { - Log.e(TAG, "Exception in getSubscriptionId"); - } - - if (isSmsSimPickActivityNeeded) { - Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true"); - // ask the user for a default SMS SIM. - Intent intent = new Intent(); - intent.setClassName("com.android.settings", - "com.android.settings.sim.SimDialogActivity"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK); - try { - context.startActivity(intent); - } catch (ActivityNotFoundException anfe) { - // If Settings is not installed, only log the error as we do not want to break - // legacy applications. - Log.e(TAG, "Unable to launch Settings application."); - } - } - - return subId; - } - - /** - * @return the subscription ID associated with this {@link SmsManager} or the default set by the - * user if this instance was created using {@link SmsManager#getDefault}. - * - * If there is no default set by the user, this method returns - * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. - */ - private int getSubIdOrDefault() { - return (mSubId == DEFAULT_SUBSCRIPTION_ID) ? getDefaultSmsSubscriptionId() : mSubId; + return (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) + ? getDefaultSmsSubscriptionId() : mSubId; } /** @@ -1172,8 +1134,9 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. - success = iSms.enableCellBroadcastForSubscriber(getSubIdOrDefault(), + // If getSubscriptionId() returns INVALID or an inactive subscription, we will use + // the default phone internally. + success = iSms.enableCellBroadcastForSubscriber(getSubscriptionId(), messageIdentifier, ranType); } } catch (RemoteException ex) { @@ -1209,8 +1172,9 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. - success = iSms.disableCellBroadcastForSubscriber(getSubIdOrDefault(), + // If getSubscriptionId() returns INVALID or an inactive subscription, we will use + // the default phone internally. + success = iSms.disableCellBroadcastForSubscriber(getSubscriptionId(), messageIdentifier, ranType); } } catch (RemoteException ex) { @@ -1253,8 +1217,9 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. - success = iSms.enableCellBroadcastRangeForSubscriber(getSubIdOrDefault(), + // If getSubscriptionId() returns INVALID or an inactive subscription, we will use + // the default phone internally. + success = iSms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(), startMessageId, endMessageId, ranType); } } catch (RemoteException ex) { @@ -1297,8 +1262,9 @@ public final class SmsManager { try { ISms iSms = getISmsService(); if (iSms != null) { - // If getSubIdOrDefault() returns INVALID, we will use the default phone internally. - success = iSms.disableCellBroadcastRangeForSubscriber(getSubIdOrDefault(), + // If getSubscriptionId() returns INVALID or an inactive subscription, we will use + // the default phone internally. + success = iSms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(), startMessageId, endMessageId, ranType); } } catch (RemoteException ex) { @@ -1386,7 +1352,8 @@ public final class SmsManager { /** * Get default sms subscription id * - * @return the default SMS subscription id + * @return the default SMS subscription id or + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if no default is set. */ public static int getDefaultSmsSubscriptionId() { ISms iSms = null; From 05f4ff0a9549afb99f10b01d0813daaa4fae446d Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Tue, 30 Apr 2019 11:37:27 -0700 Subject: [PATCH 2/2] Trampoline SMS disambig dialog to Telephony SmsManager used to display a "select default SMS subscription" dialog every time SmsManager was used and no default SMS subscription was set. This change fixes the following issues: 1_ getSubscriptionId no longer pops up a disambig dialog. 2) The disambig dialog no longer sets the default, rather it waits for the response from the user and performs the operation when it receives a response. 2a) Dialogs can no longer be created from background services, so we now have a restriction that if the disambig dialog is shown via a background service, we will use phone 0 as default (backwards compat for apps targeting * + *

Note: 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. + *

+ * * * @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}). + *

+ * + *

Note: 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. + *

* * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. *

* + *

Note: 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. + *

+ * * @see #sendTextMessage(String, String, String, PendingIntent, * PendingIntent, int, boolean, int) * @hide @@ -532,6 +672,16 @@ public final class SmsManager { *

Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. * + *

Note: 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. + *

+ * * @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}.

* + *

Note: 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. + *

+ * + * * @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. + * + *

Note: 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. + *

+ * + * @param packageName serves as the default package name if + * {@link ActivityThread#currentPackageName()} is null. + * @hide */ public void sendMultipartTextMessageExternal( String destinationAddress, String scAddress, ArrayList 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. * + *

+ * 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. + *

+ * *

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}.

* + *

Note: 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. + *

+ * @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. *

* + *

Note: 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. + *

+ * * @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. * *

Note: Using this method requires that your app has the * {@link android.Manifest.permission#SEND_SMS} permission.

* + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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 + *

Note: 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. + *

+ * + *

Note: 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. + *

+ * + * @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. + * Get 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()}. - * - * @param subId an SMS subscription ID, typically accessed using {@link SubscriptionManager} - * @return the instance of the SmsManager associated with subscription + *

Note: Constructing an {@link SmsManager} in this manner will + * never cause an SMS disambiguation dialog to appear, unlike {@link #getDefault()}. + *

* * @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}. + *

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 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. + *

* * @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 + // 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 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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @return ArrayList of SmsMessage 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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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 SmsMessages from a list of RawSmsData * records returned by getAllMessagesFromIcc() * + *

Note: 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. + *

+ * * @param records SMS EF records, returned by * getAllMessagesFromIcc * @return ArrayList of SmsMessage objects. @@ -1305,6 +1894,16 @@ public final class SmsManager { * SMS over IMS is supported if IMS is registered and SMS is supported * on IMS. * + *

Note: 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. + *

+ * * @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. + * + *

Note: 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. + *

* * @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 * + *

Note: 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. + *

+ * * @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 * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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 PendingIntent 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 PendingIntent lists should match the part number of the * divided text of the stored message by using divideMessage * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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. * + *

Note: 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. + *

+ * * @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 0000000000000..252460e563306 --- /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 bd26e1a5f2c7f..1aba95bf5e5ad 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; @@ -1974,6 +1976,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. */