From 26b242f75e44253efd0d538b45095ec7bd1e5544 Mon Sep 17 00:00:00 2001
From: Brad Ebinger 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 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.
+ * 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.
+ * 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.
+ * 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.
+ *
*
+ *
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+ * 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 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * 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.
*
+ * SmsMessages from a list of RawSmsData
* records returned by getAllMessagesFromIcc()
*
+ * 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.
*
+ * 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
*
+ *