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