blob: b44e4f179392a6d8b266b8efb9817224ad8c8e32 [file] [log] [blame]
/*
* Copyright (C) 2008 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 android.telephony;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
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.database.CursorWindow;
import android.net.Uri;
import android.os.BaseBundle;
import android.os.Binder;
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.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/*
* TODO(code review): Curious question... Why are a lot of these
* methods not declared as static, since they do not seem to require
* any local object state? Presumably this cannot be changed without
* interfering with the API...
*/
/**
* Manages SMS operations such as sending data, text, and pdu SMS messages.
* Get this object by calling the static method {@link #getDefault()}. To create an instance of
* {@link SmsManager} associated with a specific subscription ID, call
* {@link #getSmsManagerForSubscriptionId(int)}. This is typically used for devices that support
* multiple active subscriptions at once.
*
* <p>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";
/** Singleton object constructed during class initialization. */
private static final SmsManager sInstance = new SmsManager(
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
private static final Object sLockObject = new Object();
/** @hide */
public static final int CELL_BROADCAST_RAN_TYPE_GSM = 0;
/** @hide */
public static final int CELL_BROADCAST_RAN_TYPE_CDMA = 1;
/** SMS record length from TS 51.011 10.5.3
* @hide
*/
public static final int SMS_RECORD_LENGTH = 176;
/** SMS record length from C.S0023 3.4.27
* @hide
*/
public static final int CDMA_SMS_RECORD_LENGTH = 255;
private static final Map<Integer, SmsManager> sSubInstances =
new ArrayMap<Integer, SmsManager>();
/** A concrete subscription id, or the pseudo DEFAULT_SUBSCRIPTION_ID */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mSubId;
/*
* Key for the various carrier-dependent configuration values.
* Some of the values are used by the system in processing SMS or MMS messages. Others
* are provided for the convenience of SMS applications.
*/
/**
* Whether to append transaction id to MMS WAP Push M-Notification.ind's content location URI
* when constructing the download URL of a new MMS (boolean type)
*/
public static final String MMS_CONFIG_APPEND_TRANSACTION_ID =
CarrierConfigManager.KEY_MMS_APPEND_TRANSACTION_ID_BOOL;
/**
* Whether MMS is enabled for the current carrier (boolean type)
*/
public static final String
MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL;
/**
* Whether group MMS is enabled for the current carrier (boolean type)
*/
public static final String
MMS_CONFIG_GROUP_MMS_ENABLED = CarrierConfigManager.KEY_MMS_GROUP_MMS_ENABLED_BOOL;
/**
* If this is enabled, M-NotifyResp.ind should be sent to the WAP Push content location instead
* of the default MMSC (boolean type)
*/
public static final String MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED =
CarrierConfigManager.KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL;
/**
* Whether alias is enabled (boolean type)
*/
public static final String
MMS_CONFIG_ALIAS_ENABLED = CarrierConfigManager.KEY_MMS_ALIAS_ENABLED_BOOL;
/**
* Whether audio is allowed to be attached for MMS messages (boolean type)
*/
public static final String
MMS_CONFIG_ALLOW_ATTACH_AUDIO = CarrierConfigManager.KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL;
/**
* Whether multipart SMS is enabled (boolean type)
*/
public static final String MMS_CONFIG_MULTIPART_SMS_ENABLED =
CarrierConfigManager.KEY_MMS_MULTIPART_SMS_ENABLED_BOOL;
/**
* Whether SMS delivery report is enabled (boolean type)
*/
public static final String MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED =
CarrierConfigManager.KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL;
/**
* Whether content-disposition field should be expected in an MMS PDU (boolean type)
*/
public static final String MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION =
CarrierConfigManager.KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL;
/**
* Whether multipart SMS should be sent as separate messages
*/
public static final String MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES =
CarrierConfigManager.KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL;
/**
* Whether MMS read report is enabled (boolean type)
*/
public static final String MMS_CONFIG_MMS_READ_REPORT_ENABLED =
CarrierConfigManager.KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL;
/**
* Whether MMS delivery report is enabled (boolean type)
*/
public static final String MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED =
CarrierConfigManager.KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL;
/**
* Max MMS message size in bytes (int type)
*/
public static final String
MMS_CONFIG_MAX_MESSAGE_SIZE = CarrierConfigManager.KEY_MMS_MAX_MESSAGE_SIZE_INT;
/**
* Max MMS image width (int type)
*/
public static final String
MMS_CONFIG_MAX_IMAGE_WIDTH = CarrierConfigManager.KEY_MMS_MAX_IMAGE_WIDTH_INT;
/**
* Max MMS image height (int type)
*/
public static final String
MMS_CONFIG_MAX_IMAGE_HEIGHT = CarrierConfigManager.KEY_MMS_MAX_IMAGE_HEIGHT_INT;
/**
* Limit of recipients of MMS messages (int type)
*/
public static final String
MMS_CONFIG_RECIPIENT_LIMIT = CarrierConfigManager.KEY_MMS_RECIPIENT_LIMIT_INT;
/**
* Min alias character count (int type)
*/
public static final String
MMS_CONFIG_ALIAS_MIN_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MIN_CHARS_INT;
/**
* Max alias character count (int type)
*/
public static final String
MMS_CONFIG_ALIAS_MAX_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MAX_CHARS_INT;
/**
* When the number of parts of a multipart SMS reaches this threshold, it should be converted
* into an MMS (int type)
*/
public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD =
CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT;
/**
* Some carriers require SMS to be converted into MMS when text length reaches this threshold
* (int type)
*/
public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD =
CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT;
/**
* Max message text size (int type)
*/
public static final String MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE =
CarrierConfigManager.KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT;
/**
* Max message subject length (int type)
*/
public static final String
MMS_CONFIG_SUBJECT_MAX_LENGTH = CarrierConfigManager.KEY_MMS_SUBJECT_MAX_LENGTH_INT;
/**
* MMS HTTP socket timeout in milliseconds (int type)
*/
public static final String
MMS_CONFIG_HTTP_SOCKET_TIMEOUT = CarrierConfigManager.KEY_MMS_HTTP_SOCKET_TIMEOUT_INT;
/**
* The name of the UA Prof URL HTTP header for MMS HTTP request (String type)
*/
public static final String
MMS_CONFIG_UA_PROF_TAG_NAME = CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING;
/**
* The User-Agent header value for MMS HTTP request (String type)
*/
public static final String
MMS_CONFIG_USER_AGENT = CarrierConfigManager.KEY_MMS_USER_AGENT_STRING;
/**
* The UA Profile URL header value for MMS HTTP request (String type)
*/
public static final String
MMS_CONFIG_UA_PROF_URL = CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING;
/**
* A list of HTTP headers to add to MMS HTTP request, separated by "|" (String type)
*/
public static final String
MMS_CONFIG_HTTP_PARAMS = CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING;
/**
* Email gateway number (String type)
*/
public static final String MMS_CONFIG_EMAIL_GATEWAY_NUMBER =
CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING;
/**
* The suffix to append to the NAI header value for MMS HTTP request (String type)
*/
public static final String
MMS_CONFIG_NAI_SUFFIX = CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING;
/**
* If true, show the cell broadcast (amber alert) in the SMS settings. Some carriers don't want
* this shown. (Boolean type)
*/
public static final String MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS =
CarrierConfigManager.KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL;
/**
* Whether the carrier MMSC supports charset field in Content-Type header. If this is false,
* then we don't add "charset" to "Content-Type"
*/
public static final String MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER =
CarrierConfigManager.KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL;
/**
* If true, add "Connection: close" header to MMS HTTP requests so the connection
* is immediately closed (disabling keep-alive). (Boolean type)
* @hide
*/
public static final String MMS_CONFIG_CLOSE_CONNECTION =
CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL;
/**
* 3gpp2 SMS priority is not specified
* @hide
*/
public static final int SMS_MESSAGE_PRIORITY_NOT_SPECIFIED = -1;
/**
* 3gpp SMS period is not specified
* @hide
*/
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
* {@link android.Manifest.permission#SEND_SMS} permission.</p>
*
* <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
* <em>and only if</em> an app is not selected as the default SMS app, the system automatically
* writes messages sent using this method to the SMS Provider (the default SMS app is always
* 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
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* <code>RESULT_ERROR_NO_SERVICE</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
*
* @throws IllegalArgumentException if destinationAddress or text are empty
*/
public void sendTextMessage(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
true /* persistMessage*/, ActivityThread.currentPackageName());
}
private void sendTextMessageInternal(String destinationAddress, String scAddress,
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessage, String packageName) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
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();
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);
}
}
}
/**
* Send a text based SMS without writing it into the SMS Provider.
*
* <p>
* The message will be sent directly over the network and will not be visible in SMS
* applications. Intended for internal carrier use only.
* </p>
*
* <p>Requires Permission: Both {@link android.Manifest.permission#SEND_SMS} and
* {@link android.Manifest.permission#MODIFY_PHONE_STATE}, or that the calling app has carrier
* 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)
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
android.Manifest.permission.MODIFY_PHONE_STATE,
android.Manifest.permission.SEND_SMS
})
public void sendTextMessageWithoutPersisting(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
false /* persistMessage */, ActivityThread.currentPackageName());
}
/**
* 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.
*
* @hide
*/
public void sendTextMessageWithSelfPermissions(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
try {
ISms iSms = getISmsServiceOrThrow();
iSms.sendTextForSubscriberWithSelfPermissions(getSubscriptionId(),
ActivityThread.currentPackageName(),
destinationAddress,
scAddress, text, sentIntent, deliveryIntent, persistMessage);
} catch (RemoteException ex) {
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
* @param text the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
* @param priority Priority level of the message
* Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
* ---------------------------------
* PRIORITY | Level of Priority
* ---------------------------------
* '00' | Normal
* '01' | Interactive
* '10' | Urgent
* '11' | Emergency
* ----------------------------------
* Any Other values included Negative considered as Invalid Priority Indicator of the message.
* @param expectMore is a boolean to indicate the sending messages through same link or not.
* @param validityPeriod Validity Period of the message in mins.
* Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
* Validity Period(Minimum) -> 5 mins
* Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
* Any Other values included Negative considered as Invalid Validity Period of the message.
*
* @throws IllegalArgumentException if destinationAddress or text are empty
* {@hide}
*/
@UnsupportedAppUsage
public void sendTextMessage(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent,
int priority, boolean expectMore, int validityPeriod) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
true /* persistMessage*/, priority, expectMore, validityPeriod);
}
private void sendTextMessageInternal(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
int priority, boolean expectMore, int validityPeriod) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
if (priority < 0x00 || priority > 0x03) {
priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
}
if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
}
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);
}
}
}
/**
* Send a text based SMS without writing it into the SMS Provider.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
* 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
*/
@UnsupportedAppUsage
public void sendTextMessageWithoutPersisting(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
boolean expectMore, int validityPeriod) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
false /* persistMessage */, priority, expectMore, validityPeriod);
}
/**
*
* Inject an SMS PDU into the android application framework.
*
* <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})
* @param receivedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully received by the
* android application framework, or failed. This intent is broadcasted at
* the same time an SMS received from radio is acknowledged back.
* The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED}
* for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for
* error.
*
* @throws IllegalArgumentException if the format is invalid.
*/
public void injectSmsPdu(
byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
// Format must be either 3gpp or 3gpp2.
throw new IllegalArgumentException(
"Invalid pdu format. format must be either 3gpp or 3gpp2");
}
try {
ISms iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iSms != null) {
iSms.injectSmsPduForSubscriber(
getSubscriptionId(), pdu, format, receivedIntent);
}
} catch (RemoteException ex) {
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.
}
}
}
/**
* Divide a message text into several fragments, none bigger than the maximum SMS message size.
*
* @param text the original message. Must not be null.
* @return an <code>ArrayList</code> of strings that, in order, comprise the original message.
* @throws IllegalArgumentException if text is null.
*/
public ArrayList<String> divideMessage(String text) {
if (null == text) {
throw new IllegalArgumentException("text is null");
}
return SmsMessage.fragmentText(text, getSubscriptionId());
}
/**
* Send a multi-part text based SMS. The callee should have already
* divided the message into correctly sized parts by calling
* <code>divideMessage</code>.
*
* <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> Beginning with Android 4.4 (API level 19), if
* <em>and only if</em> an app is not selected as the default SMS app, the system automatically
* writes messages sent using this method to the SMS Provider (the default SMS app is always
* 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
* @param parts an <code>ArrayList</code> of strings that, in order,
* comprise the original message
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been delivered
* to the recipient. The raw pdu of the status report is in the
* extended data ("pdu").
*
* @throws IllegalArgumentException if destinationAddress or data are empty
*/
public void sendMultipartTextMessage(
String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
deliveryIntents, true /* persistMessage*/, ActivityThread.currentPackageName());
}
/**
* Similar method as #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
* 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,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
String packageName) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
deliveryIntents, true /* persistMessage*/,
ActivityThread.currentPackageName() == null
? packageName : ActivityThread.currentPackageName());
}
private void sendMultipartTextMessageInternal(
String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
boolean persistMessage, String packageName) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (parts == null || parts.size() < 1) {
throw new IllegalArgumentException("Invalid message body");
}
if (parts.size() > 1) {
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;
PendingIntent deliveryIntent = null;
if (sentIntents != null && sentIntents.size() > 0) {
sentIntent = sentIntents.get(0);
}
if (deliveryIntents != null && deliveryIntents.size() > 0) {
deliveryIntent = deliveryIntents.get(0);
}
sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
sentIntent, deliveryIntent, true, packageName);
}
}
/**
* 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.
* </p>
*
* @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
* @hide
**/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void sendMultipartTextMessageWithoutPersisting(
String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
deliveryIntents, false /* persistMessage*/, ActivityThread.currentPackageName());
}
/**
* Send a multi-part text based SMS with messaging options. The callee should have already
* divided the message into correctly sized parts by calling
* <code>divideMessage</code>.
*
* <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> Beginning with Android 4.4 (API level 19), if
* <em>and only if</em> an app is not selected as the default SMS app, the system automatically
* writes messages sent using this method to the SMS Provider (the default SMS app is always
* 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
* @param parts an <code>ArrayList</code> of strings that, in order,
* comprise the original message
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been delivered
* to the recipient. The raw pdu of the status report is in the
* extended data ("pdu").
* @param priority Priority level of the message
* Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
* ---------------------------------
* PRIORITY | Level of Priority
* ---------------------------------
* '00' | Normal
* '01' | Interactive
* '10' | Urgent
* '11' | Emergency
* ----------------------------------
* Any Other values included Negative considered as Invalid Priority Indicator of the message.
* @param expectMore is a boolean to indicate the sending messages through same link or not.
* @param validityPeriod Validity Period of the message in mins.
* Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
* Validity Period(Minimum) -> 5 mins
* Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
* Any Other values included Negative considered as Invalid Validity Period of the message.
*
* @throws IllegalArgumentException if destinationAddress or data are empty
* {@hide}
*/
@UnsupportedAppUsage
public void sendMultipartTextMessage(
String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
int priority, boolean expectMore, int validityPeriod) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
deliveryIntents, true /* persistMessage*/, priority, expectMore,
validityPeriod);
}
private void sendMultipartTextMessageInternal(
String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (parts == null || parts.size() < 1) {
throw new IllegalArgumentException("Invalid message body");
}
if (priority < 0x00 || priority > 0x03) {
priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
}
if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
}
if (parts.size() > 1) {
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);
}
}
} else {
PendingIntent sentIntent = null;
PendingIntent deliveryIntent = null;
if (sentIntents != null && sentIntents.size() > 0) {
sentIntent = sentIntents.get(0);
}
if (deliveryIntents != null && deliveryIntents.size() > 0) {
deliveryIntent = deliveryIntents.get(0);
}
sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
sentIntent, deliveryIntent, persistMessage, priority, expectMore,
validityPeriod);
}
}
/**
* Send a multi-part text based SMS without writing it into the SMS Provider.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
* 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
**/
public void sendMultipartTextMessageWithoutPersisting(
String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
int priority, boolean expectMore, int validityPeriod) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
deliveryIntents, false /* persistMessage*/, priority, expectMore,
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
* @param destinationPort the port to deliver the message to
* @param data the body of the message to send
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
*
* @throws IllegalArgumentException if destinationAddress or data are empty
*/
public void sendDataMessage(
String destinationAddress, String scAddress, short destinationPort,
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (data == null || data.length == 0) {
throw new IllegalArgumentException("Invalid message data");
}
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(
String destinationAddress, String scAddress, short destinationPort,
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (data == null || data.length == 0) {
throw new IllegalArgumentException("Invalid message data");
}
try {
ISms iSms = getISmsServiceOrThrow();
iSms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(),
ActivityThread.currentPackageName(), destinationAddress, scAddress,
destinationPort & 0xFFFF, data, sentIntent, deliveryIntent);
} catch (RemoteException e) {
Log.e(TAG, "sendDataMessageWithSelfPermissions: Couldn't send SMS - Exception: "
+ e.getMessage());
notifySmsGenericError(sentIntent);
}
}
/**
* 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.
*
* <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 instance of the SmsManager associated with a particular subscription ID.
*
* <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>
*
* @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) {
synchronized(sLockObject) {
SmsManager smsManager = sSubInstances.get(subId);
if (smsManager == null) {
smsManager = new SmsManager(subId);
sSubInstances.put(subId, smsManager);
}
return smsManager;
}
}
private SmsManager(int subId) {
mSubId = subId;
}
/**
* 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).
*
* <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 has multiple active
* subscriptions and and no default is set ("ask every time") by the user.
*/
public int getSubscriptionId() {
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.
}
}
}
}
/**
* Returns the ISms service, or throws an UnsupportedOperationException if
* the service does not exist.
*/
private static ISms getISmsServiceOrThrow() {
ISms iSms = getISmsService();
if (iSms == null) {
throw new UnsupportedOperationException("Sms is not supported");
}
return iSms;
}
private static ISms getISmsService() {
return ISms.Stub.asInterface(ServiceManager.getService("isms"));
}
/**
* Copy a raw SMS PDU to the ICC.
* 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,
* STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
* @return true for success
*
* @throws IllegalArgumentException if pdu is NULL
* {@hide}
*/
@UnsupportedAppUsage
public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
boolean success = false;
if (null == pdu) {
throw new IllegalArgumentException("pdu is NULL");
}
try {
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
ActivityThread.currentPackageName(),
status, pdu, smsc);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Delete the specified message from the ICC.
* 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
*
* {@hide}
*/
@UnsupportedAppUsage
public boolean
deleteMessageFromIcc(int messageIndex) {
boolean success = false;
byte[] pdu = new byte[SMS_RECORD_LENGTH-1];
Arrays.fill(pdu, (byte)0xff);
try {
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
ActivityThread.currentPackageName(),
messageIndex, STATUS_ON_ICC_FREE, pdu);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Update the specified message on the ICC.
* 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,
* STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
* @param pdu the raw PDU to store
* @return true for success
*
* {@hide}
*/
@UnsupportedAppUsage
public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
boolean success = false;
try {
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
ActivityThread.currentPackageName(),
messageIndex, newStatus, pdu);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Retrieves all messages currently stored on ICC.
* 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}
*/
@UnsupportedAppUsage
public ArrayList<SmsMessage> getAllMessagesFromIcc() {
List<SmsRawData> records = null;
try {
ISms iSms = getISmsService();
if (iSms != null) {
records = iSms.getAllMessagesFromIccEfForSubscriber(
getSubscriptionId(),
ActivityThread.currentPackageName());
}
} catch (RemoteException ex) {
// ignore it
}
return createMessageListFromRawRecords(records);
}
/**
* Enable reception of cell broadcast (SMS-CB) messages with the given
* message identifier and RAN type. The RAN type specify this message ID
* belong to 3GPP (GSM) or 3GPP2(CDMA).Note that if two different clients
* enable the same message identifier, they must both disable it for the device to stop
* receiving those messages. All received messages will be broadcast in an
* intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
* 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:
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
* @return true if successful, false otherwise
* @see #disableCellBroadcast(int, int)
*
* {@hide}
*/
public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
boolean success = false;
try {
ISms iSms = getISmsService();
if (iSms != null) {
// If getSubscriptionId() returns INVALID or an inactive subscription, we will use
// the default phone internally.
success = iSms.enableCellBroadcastForSubscriber(getSubscriptionId(),
messageIdentifier, ranType);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Disable reception of cell broadcast (SMS-CB) messages with the given
* message identifier and RAN type. The RAN type specify this message ID
* belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
* enable the same message identifier, they must both disable it for the
* device to stop receiving those messages.
* 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:
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
* @return true if successful, false otherwise
*
* @see #enableCellBroadcast(int, int)
*
* {@hide}
*/
public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
boolean success = false;
try {
ISms iSms = getISmsService();
if (iSms != null) {
// If getSubscriptionId() returns INVALID or an inactive subscription, we will use
// the default phone internally.
success = iSms.disableCellBroadcastForSubscriber(getSubscriptionId(),
messageIdentifier, ranType);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Enable reception of cell broadcast (SMS-CB) messages with the given
* message identifier range and RAN type. The RAN type specify this message ID
* belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
* the same message identifier, they must both disable it for the device to stop
* receiving those messages. All received messages will be broadcast in an
* intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
* 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)
* or C.R1001-G (3GPP2)
* @param ranType as defined in class SmsManager, the value can be one of these:
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
* @return true if successful, false otherwise
* @see #disableCellBroadcastRange(int, int, int)
*
* @throws IllegalArgumentException if endMessageId < startMessageId
* {@hide}
*/
@UnsupportedAppUsage
public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
boolean success = false;
if (endMessageId < startMessageId) {
throw new IllegalArgumentException("endMessageId < startMessageId");
}
try {
ISms iSms = getISmsService();
if (iSms != null) {
// 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) {
// ignore it
}
return success;
}
/**
* Disable reception of cell broadcast (SMS-CB) messages with the given
* message identifier range and RAN type. The RAN type specify this message
* ID range belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different
* clients enable the same message identifier, they must both disable it for
* the device to stop receiving those messages.
* 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)
* or C.R1001-G (3GPP2)
* @param ranType as defined in class SmsManager, the value can be one of these:
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
* android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
* @return true if successful, false otherwise
*
* @see #enableCellBroadcastRange(int, int, int)
*
* @throws IllegalArgumentException if endMessageId < startMessageId
* {@hide}
*/
@UnsupportedAppUsage
public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
boolean success = false;
if (endMessageId < startMessageId) {
throw new IllegalArgumentException("endMessageId < startMessageId");
}
try {
ISms iSms = getISmsService();
if (iSms != null) {
// 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) {
// ignore it
}
return success;
}
/**
* 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.
*/
private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
if (records != null) {
int count = records.size();
for (int i = 0; i < count; i++) {
SmsRawData data = records.get(i);
// List contains all records, including "free" records (null)
if (data != null) {
SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
getSubscriptionId());
if (sms != null) {
messages.add(sms);
}
}
}
}
return messages;
}
/**
* 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()
*
* @hide
*/
public boolean isImsSmsSupported() {
boolean boSupported = false;
try {
ISms iSms = getISmsService();
if (iSms != null) {
boSupported = iSms.isImsSmsSupportedForSubscriber(getSubscriptionId());
}
} catch (RemoteException ex) {
// ignore it
}
return boSupported;
}
/**
* 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
* or SmsMessage.FORMAT_UNKNOWN
*
* @see #isImsSmsSupported()
*
* @hide
*/
public String getImsSmsFormat() {
String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
try {
ISms iSms = getISmsService();
if (iSms != null) {
format = iSms.getImsSmsFormatForSubscriber(getSubscriptionId());
}
} catch (RemoteException ex) {
// ignore it
}
return format;
}
/**
* Get default sms subscription id.
*
* <p class="note"><strong>Note:</strong>This returns a value different from
* {@link SubscriptionManager#getDefaultSmsSubscriptionId} if the user has not chosen a default.
* In this case it returns the active subscription id if there's only one active subscription
* available.
*
* @return the user-defined default SMS subscription id, or the active subscription id if
* there's only one active subscription available, otherwise
* {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
*/
public static int getDefaultSmsSubscriptionId() {
try {
return getISmsService().getPreferredSmsSubscription();
} catch (RemoteException e) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
} catch (NullPointerException e) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
}
/**
* Get SMS prompt property, enabled or not
*
* @return true if enabled, false otherwise
* @hide
*/
@UnsupportedAppUsage
public boolean isSMSPromptEnabled() {
ISms iSms = null;
try {
iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
return iSms.isSMSPromptEnabled();
} catch (RemoteException ex) {
return false;
} catch (NullPointerException ex) {
return false;
}
}
// see SmsMessage.getStatusOnIcc
/** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
static public final int STATUS_ON_ICC_FREE = 0;
/** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
static public final int STATUS_ON_ICC_READ = 1;
/** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
static public final int STATUS_ON_ICC_UNREAD = 3;
/** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
static public final int STATUS_ON_ICC_SENT = 5;
/** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
static public final int STATUS_ON_ICC_UNSENT = 7;
// SMS send failure result codes
/**
* No error.
* @hide
*/
@SystemApi
static public final int RESULT_ERROR_NONE = 0;
/** Generic failure cause */
static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
/** Failed because radio was explicitly turned off */
static public final int RESULT_ERROR_RADIO_OFF = 2;
/** Failed because no pdu provided */
static public final int RESULT_ERROR_NULL_PDU = 3;
/** Failed because service is currently unavailable */
static public final int RESULT_ERROR_NO_SERVICE = 4;
/** Failed because we reached the sending queue limit. */
static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5;
/**
* Failed because FDN is enabled.
* @hide
*/
@SystemApi
static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6;
/** Failed because user denied the sending of this short code. */
static public final int RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 7;
/** Failed because the user has denied this app ever send premium short codes. */
static public final int RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 8;
/**
* Failed because the radio was not available
* @hide
*/
@SystemApi
static public final int RESULT_RADIO_NOT_AVAILABLE = 9;
/**
* Failed because of network rejection
* @hide
*/
@SystemApi
static public final int RESULT_NETWORK_REJECT = 10;
/**
* Failed because of invalid arguments
* @hide
*/
@SystemApi
static public final int RESULT_INVALID_ARGUMENTS = 11;
/**
* Failed because of an invalid state
* @hide
*/
@SystemApi
static public final int RESULT_INVALID_STATE = 12;
/**
* Failed because there is no memory
* @hide
*/
@SystemApi
static public final int RESULT_NO_MEMORY = 13;
/**
* Failed because the sms format is not valid
* @hide
*/
@SystemApi
static public final int RESULT_INVALID_SMS_FORMAT = 14;
/**
* Failed because of a system error
* @hide
*/
@SystemApi
static public final int RESULT_SYSTEM_ERROR = 15;
/**
* Failed because of a modem error
* @hide
*/
@SystemApi
static public final int RESULT_MODEM_ERROR = 16;
/**
* Failed because of a network error
* @hide
*/
@SystemApi
static public final int RESULT_NETWORK_ERROR = 17;
/**
* Failed because of an encoding error
* @hide
*/
@SystemApi
static public final int RESULT_ENCODING_ERROR = 18;
/**
* Failed because of an invalid smsc address
* @hide
*/
@SystemApi
static public final int RESULT_INVALID_SMSC_ADDRESS = 19;
/**
* Failed because the operation is not allowed
* @hide
*/
@SystemApi
static public final int RESULT_OPERATION_NOT_ALLOWED = 20;
/**
* Failed because of an internal error
* @hide
*/
@SystemApi
static public final int RESULT_INTERNAL_ERROR = 21;
/**
* Failed because there are no resources
* @hide
*/
@SystemApi
static public final int RESULT_NO_RESOURCES = 22;
/**
* Failed because the operation was cancelled
* @hide
*/
@SystemApi
static public final int RESULT_CANCELLED = 23;
/**
* Failed because the request is not supported
* @hide
*/
@SystemApi
static public final int RESULT_REQUEST_NOT_SUPPORTED = 24;
static private final String PHONE_PACKAGE_NAME = "com.android.phone";
/**
* 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
* @param configOverrides the carrier-specific messaging configuration values to override for
* sending the message.
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
* @throws IllegalArgumentException if contentUri is empty
*/
public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
Bundle configOverrides, PendingIntent sentIntent) {
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
try {
final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms == null) {
return;
}
iMms.sendMessage(getSubscriptionId(), ActivityThread.currentPackageName(), contentUri,
locationUrl, configOverrides, sentIntent);
} catch (RemoteException e) {
// Ignore it
}
}
/**
* 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
* @param contentUri the content uri to which the downloaded pdu will be written
* @param configOverrides the carrier-specific messaging configuration values to override for
* downloading the message.
* @param downloadedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is downloaded, or the download is failed
* @throws IllegalArgumentException if locationUrl or contentUri is empty
*/
public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
Bundle configOverrides, PendingIntent downloadedIntent) {
if (TextUtils.isEmpty(locationUrl)) {
throw new IllegalArgumentException("Empty MMS location URL");
}
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
try {
final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms == null) {
return;
}
iMms.downloadMessage(getSubscriptionId(), ActivityThread.currentPackageName(),
locationUrl, contentUri, configOverrides, downloadedIntent);
} catch (RemoteException e) {
// Ignore it
}
}
// MMS send/download failure result codes
public static final int MMS_ERROR_UNSPECIFIED = 1;
public static final int MMS_ERROR_INVALID_APN = 2;
public static final int MMS_ERROR_UNABLE_CONNECT_MMS = 3;
public static final int MMS_ERROR_HTTP_FAILURE = 4;
public static final int MMS_ERROR_IO_ERROR = 5;
public static final int MMS_ERROR_RETRY = 6;
public static final int MMS_ERROR_CONFIGURATION_ERROR = 7;
public static final int MMS_ERROR_NO_DATA_NETWORK = 8;
/** Intent extra name for MMS sending result data in byte array type */
public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
/** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
/**
* Import a text message into system's SMS store
*
* Only default SMS apps can import SMS
*
* @param address the destination(source) address of the sent(received) message
* @param type the type of the message
* @param text the message text
* @param timestampMillis the message timestamp in milliseconds
* @param seen if the message is seen
* @param read if the message is read
* @return the message URI, null if failed
* @hide
*/
public Uri importTextMessage(String address, int type, String text, long timestampMillis,
boolean seen, boolean read) {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.importTextMessage(ActivityThread.currentPackageName(),
address, type, text, timestampMillis, seen, read);
}
} catch (RemoteException ex) {
// ignore it
}
return null;
}
/** Represents the received SMS message for importing {@hide} */
public static final int SMS_TYPE_INCOMING = 0;
/** Represents the sent SMS message for importing {@hide} */
public static final int SMS_TYPE_OUTGOING = 1;
/**
* Import a multimedia message into system's MMS store. Only the following PDU type is
* supported: Retrieve.conf, Send.req, Notification.ind, Delivery.ind, Read-Orig.ind
*
* Only default SMS apps can import MMS
*
* @param contentUri the content uri from which to read the PDU of the message to import
* @param messageId the optional message id. Use null if not specifying
* @param timestampSecs the optional message timestamp. Use -1 if not specifying
* @param seen if the message is seen
* @param read if the message is read
* @return the message URI, null if failed
* @throws IllegalArgumentException if pdu is empty
* {@hide}
*/
public Uri importMultimediaMessage(Uri contentUri, String messageId, long timestampSecs,
boolean seen, boolean read) {
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.importMultimediaMessage(ActivityThread.currentPackageName(),
contentUri, messageId, timestampSecs, seen, read);
}
} catch (RemoteException ex) {
// ignore it
}
return null;
}
/**
* Delete a system stored SMS or MMS message
*
* Only default SMS apps can delete system stored SMS and MMS messages
*
* @param messageUri the URI of the stored message
* @return true if deletion is successful, false otherwise
* @throws IllegalArgumentException if messageUri is empty
* {@hide}
*/
public boolean deleteStoredMessage(Uri messageUri) {
if (messageUri == null) {
throw new IllegalArgumentException("Empty message URI");
}
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.deleteStoredMessage(ActivityThread.currentPackageName(), messageUri);
}
} catch (RemoteException ex) {
// ignore it
}
return false;
}
/**
* Delete a system stored SMS or MMS thread
*
* Only default SMS apps can delete system stored SMS and MMS conversations
*
* @param conversationId the ID of the message conversation
* @return true if deletion is successful, false otherwise
* {@hide}
*/
public boolean deleteStoredConversation(long conversationId) {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.deleteStoredConversation(
ActivityThread.currentPackageName(), conversationId);
}
} catch (RemoteException ex) {
// ignore it
}
return false;
}
/**
* Update the status properties of a system stored SMS or MMS message, e.g.
* the read status of a message, etc.
*
* @param messageUri the URI of the stored message
* @param statusValues a list of status properties in key-value pairs to update
* @return true if update is successful, false otherwise
* @throws IllegalArgumentException if messageUri is empty
* {@hide}
*/
public boolean updateStoredMessageStatus(Uri messageUri, ContentValues statusValues) {
if (messageUri == null) {
throw new IllegalArgumentException("Empty message URI");
}
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.updateStoredMessageStatus(ActivityThread.currentPackageName(),
messageUri, statusValues);
}
} catch (RemoteException ex) {
// ignore it
}
return false;
}
/** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */
public static final String MESSAGE_STATUS_SEEN = "seen";
/** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */
public static final String MESSAGE_STATUS_READ = "read";
/**
* Archive or unarchive a stored conversation
*
* @param conversationId the ID of the message conversation
* @param archived true to archive the conversation, false to unarchive
* @return true if update is successful, false otherwise
* {@hide}
*/
public boolean archiveStoredConversation(long conversationId, boolean archived) {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.archiveStoredConversation(ActivityThread.currentPackageName(),
conversationId, archived);
}
} catch (RemoteException ex) {
// ignore it
}
return false;
}
/**
* Add a text message draft to system SMS store
*
* Only default SMS apps can add SMS draft
*
* @param address the destination address of message
* @param text the body of the message to send
* @return the URI of the stored draft message
* {@hide}
*/
public Uri addTextMessageDraft(String address, String text) {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.addTextMessageDraft(ActivityThread.currentPackageName(), address, text);
}
} catch (RemoteException ex) {
// ignore it
}
return null;
}
/**
* Add a multimedia message draft to system MMS store
*
* Only default SMS apps can add MMS draft
*
* @param contentUri the content uri from which to read the PDU data of the draft MMS
* @return the URI of the stored draft message
* @throws IllegalArgumentException if pdu is empty
* {@hide}
*/
public Uri addMultimediaMessageDraft(Uri contentUri) {
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.addMultimediaMessageDraft(ActivityThread.currentPackageName(),
contentUri);
}
} catch (RemoteException ex) {
// ignore it
}
return null;
}
/**
* Send a system stored text message.
*
* 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
* broadcast when the message is successfully sent, or failed.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
*
* @throws IllegalArgumentException if messageUri is empty
* {@hide}
*/
public void sendStoredTextMessage(Uri messageUri, String scAddress, PendingIntent sentIntent,
PendingIntent deliveryIntent) {
if (messageUri == null) {
throw new IllegalArgumentException("Empty message URI");
}
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);
}
});
}
/**
* Send a system stored multi-part text message.
*
* You can only send a failed text message or a draft text message.
* 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
* @param sentIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be <code>Activity.RESULT_OK</code> for success,
* or one of these errors:<br>
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
* <code>RESULT_ERROR_RADIO_OFF</code><br>
* <code>RESULT_ERROR_NULL_PDU</code><br>
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.<br>
* The per-application based SMS control checks sentIntent. If sentIntent
* is NULL the caller will be checked against all unknown applications,
* which cause smaller number of SMS to be sent in checking period.
* @param deliveryIntents if not null, an <code>ArrayList</code> of
* <code>PendingIntent</code>s (one for each message part) that is
* broadcast when the corresponding message part has been delivered
* to the recipient. The raw pdu of the status report is in the
* extended data ("pdu").
*
* @throws IllegalArgumentException if messageUri is empty
* {@hide}
*/
public void sendStoredMultipartTextMessage(Uri messageUri, String scAddress,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
if (messageUri == null) {
throw new IllegalArgumentException("Empty message URI");
}
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);
}
});
}
/**
* Send a system stored MMS message
*
* 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.
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
* @throws IllegalArgumentException if messageUri is empty
* {@hide}
*/
public void sendStoredMultimediaMessage(Uri messageUri, Bundle configOverrides,
PendingIntent sentIntent) {
if (messageUri == null) {
throw new IllegalArgumentException("Empty message URI");
}
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
iMms.sendStoredMessage(
getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
configOverrides, sentIntent);
}
} catch (RemoteException ex) {
// ignore it
}
}
/**
* Turns on/off the flag to automatically write sent/received SMS/MMS messages into system
*
* When this flag is on, all SMS/MMS sent/received are stored by system automatically
* When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
* automatically
*
* This flag can only be changed by default SMS apps
*
* @param enabled Whether to enable message auto persisting
* {@hide}
*/
public void setAutoPersisting(boolean enabled) {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
iMms.setAutoPersisting(ActivityThread.currentPackageName(), enabled);
}
} catch (RemoteException ex) {
// ignore it
}
}
/**
* Get the value of the flag to automatically write sent/received SMS/MMS messages into system
*
* When this flag is on, all SMS/MMS sent/received are stored by system automatically
* When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
* automatically
*
* @return the current value of the auto persist flag
* {@hide}
*/
public boolean getAutoPersisting() {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.getAutoPersisting();
}
} catch (RemoteException ex) {
// ignore it
}
return false;
}
/**
* 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() {
try {
IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms != null) {
return iMms.getCarrierConfigValues(getSubscriptionId());
}
} catch (RemoteException ex) {
// ignore it
}
return null;
}
/**
* 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.
*
* The token is only good for one use, after an SMS has been received containing the token all
* subsequent SMS messages with the token will be routed as normal.
*
* 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
*/
public String createAppSpecificSmsToken(PendingIntent intent) {
try {
ISms iccSms = getISmsServiceOrThrow();
return iccSms.createAppSpecificSmsToken(getSubscriptionId(),
ActivityThread.currentPackageName(), intent);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
return null;
}
}
/** callback for providing asynchronous sms messages for financial app. */
public abstract static class FinancialSmsCallback {
/**
* Callback to send sms messages back to financial app asynchronously.
*
* @param msgs SMS messages.
*/
public abstract void onFinancialSmsMessages(CursorWindow msgs);
};
/**
* Get SMS messages for the calling financial app.
* The result will be delivered asynchronously in the passing in callback interface.
*
* <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 params the parameters to filter SMS messages returned.
* @param executor the executor on which callback will be invoked.
* @param callback a callback to receive CursorWindow with SMS messages.
*/
@RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS)
public void getSmsMessagesForFinancialApp(
Bundle params,
@NonNull @CallbackExecutor Executor executor,
@NonNull FinancialSmsCallback callback) {
try {
ISms iccSms = getISmsServiceOrThrow();
iccSms.getSmsMessagesForFinancialApp(
getSubscriptionId(), ActivityThread.currentPackageName(), params,
new IFinancialSmsCallback.Stub() {
public void onGetSmsMessagesForFinancialApp(CursorWindow msgs) {
Binder.withCleanCallingIdentity(() -> executor.execute(
() -> callback.onFinancialSmsMessages(msgs)));
}});
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
}
/**
* @see #createAppSpecificSmsTokenWithPackageInfo().
* The prefixes is a list of prefix {@code String} separated by this delimiter.
* @hide
*/
public static final String REGEX_PREFIX_DELIMITER = ",";
/**
* @see #createAppSpecificSmsTokenWithPackageInfo().
* The success status to be added into the intent to be sent to the calling package.
* @hide
*/
public static final int RESULT_STATUS_SUCCESS = 0;
/**
* @see #createAppSpecificSmsTokenWithPackageInfo().
* The timeout status to be added into the intent to be sent to the calling package.
* @hide
*/
public static final int RESULT_STATUS_TIMEOUT = 1;
/**
* @see #createAppSpecificSmsTokenWithPackageInfo().
* Intent extra key of the retrieved SMS message as a {@code String}.
* @hide
*/
public static final String EXTRA_SMS_MESSAGE = "android.telephony.extra.SMS_MESSAGE";
/**
* @see #createAppSpecificSmsTokenWithPackageInfo().
* Intent extra key of SMS retriever status, which indicates whether the request for the
* coming SMS message is SUCCESS or TIMEOUT
* @hide
*/
public static final String EXTRA_STATUS = "android.telephony.extra.STATUS";
/**
* @see #createAppSpecificSmsTokenWithPackageInfo().
* [Optional] Intent extra key of the retrieved Sim card subscription Id if any. {@code int}
* @hide
*/
public static final String EXTRA_SIM_SUBSCRIPTION_ID =
"android.telephony.extra.SIM_SUBSCRIPTION_ID";
/**
* 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, and the
* SMS message has a prefix from the given prefixes list, the provided {@code intent} will be
* sent with the SMS data to the calling package.
*
* The token is only good for one use within a reasonable amount of time. After an SMS has been
* received containing the token all subsequent SMS messages with the token will be routed as
* normal.
*
* 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>
*
* @param prefixes this is a list of prefixes string separated by REGEX_PREFIX_DELIMITER. The
* matching SMS message should have at least one of the prefixes in the beginning of the
* message.
* @param intent this intent is sent when the matching SMS message is received.
* @return Token to include in an SMS message.
*/
@Nullable
public String createAppSpecificSmsTokenWithPackageInfo(
@Nullable String prefixes, @NonNull PendingIntent intent) {
try {
ISms iccSms = getISmsServiceOrThrow();
return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
ActivityThread.currentPackageName(), prefixes, intent);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
return null;
}
}
/**
* Filters a bundle to only contain MMS config variables.
*
* This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
* config and unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the
* supplied bundle.
*
* @param config a Bundle that contains MMS config variables and possibly more.
* @return a new Bundle that only contains the MMS_CONFIG_* keys defined above.
* @hide
*/
public static Bundle getMmsConfig(BaseBundle config) {
Bundle filtered = new Bundle();
filtered.putBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID,
config.getBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID));
filtered.putBoolean(MMS_CONFIG_MMS_ENABLED, config.getBoolean(MMS_CONFIG_MMS_ENABLED));
filtered.putBoolean(MMS_CONFIG_GROUP_MMS_ENABLED,
config.getBoolean(MMS_CONFIG_GROUP_MMS_ENABLED));
filtered.putBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED,
config.getBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED));
filtered.putBoolean(MMS_CONFIG_ALIAS_ENABLED, config.getBoolean(MMS_CONFIG_ALIAS_ENABLED));
filtered.putBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO,
config.getBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO));
filtered.putBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED,
config.getBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED));
filtered.putBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED,
config.getBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED));
filtered.putBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
config.getBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION));
filtered.putBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
config.getBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES));
filtered.putBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED,
config.getBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED));
filtered.putBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED,
config.getBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED));
filtered.putBoolean(MMS_CONFIG_CLOSE_CONNECTION,
config.getBoolean(MMS_CONFIG_CLOSE_CONNECTION));
filtered.putInt(MMS_CONFIG_MAX_MESSAGE_SIZE, config.getInt(MMS_CONFIG_MAX_MESSAGE_SIZE));
filtered.putInt(MMS_CONFIG_MAX_IMAGE_WIDTH, config.getInt(MMS_CONFIG_MAX_IMAGE_WIDTH));
filtered.putInt(MMS_CONFIG_MAX_IMAGE_HEIGHT, config.getInt(MMS_CONFIG_MAX_IMAGE_HEIGHT));
filtered.putInt(MMS_CONFIG_RECIPIENT_LIMIT, config.getInt(MMS_CONFIG_RECIPIENT_LIMIT));
filtered.putInt(MMS_CONFIG_ALIAS_MIN_CHARS, config.getInt(MMS_CONFIG_ALIAS_MIN_CHARS));
filtered.putInt(MMS_CONFIG_ALIAS_MAX_CHARS, config.getInt(MMS_CONFIG_ALIAS_MAX_CHARS));
filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD));
filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD));
filtered.putInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE,
config.getInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE));
filtered.putInt(MMS_CONFIG_SUBJECT_MAX_LENGTH,
config.getInt(MMS_CONFIG_SUBJECT_MAX_LENGTH));
filtered.putInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT,
config.getInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
filtered.putString(MMS_CONFIG_UA_PROF_TAG_NAME,
config.getString(MMS_CONFIG_UA_PROF_TAG_NAME));
filtered.putString(MMS_CONFIG_USER_AGENT, config.getString(MMS_CONFIG_USER_AGENT));
filtered.putString(MMS_CONFIG_UA_PROF_URL, config.getString(MMS_CONFIG_UA_PROF_URL));
filtered.putString(MMS_CONFIG_HTTP_PARAMS, config.getString(MMS_CONFIG_HTTP_PARAMS));
filtered.putString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER,
config.getString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER));
filtered.putString(MMS_CONFIG_NAI_SUFFIX, config.getString(MMS_CONFIG_NAI_SUFFIX));
filtered.putBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS,
config.getBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS));
filtered.putBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER));
return filtered;
}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"SMS_CATEGORY_"},
value = {
SmsManager.SMS_CATEGORY_NOT_SHORT_CODE,
SmsManager.SMS_CATEGORY_FREE_SHORT_CODE,
SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE,
SmsManager.SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE,
SmsManager.SMS_CATEGORY_PREMIUM_SHORT_CODE})
public @interface SmsShortCodeCategory {}
/**
* Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for regular
* phone numbers.
* @hide
*/
@TestApi
public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0;
/**
* Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for free
* (no cost) short codes.
* @hide
*/
@TestApi
public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1;
/**
* Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for
* standard rate (non-premium)
* short codes.
* @hide
*/
@TestApi
public static final int SMS_CATEGORY_STANDARD_SHORT_CODE = 2;
/**
* Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for possible
* premium short codes.
* @hide
*/
@TestApi
public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
/**
* Return value from {@link #checkSmsShortCodeDestination(String, String)} ()} for
* premium short codes.
* @hide
*/
@TestApi
public static final int SMS_CATEGORY_PREMIUM_SHORT_CODE = 4;
/**
* Check if the destination address is a possible premium short code.
* NOTE: the caller is expected to strip non-digits from the destination number with
* {@link PhoneNumberUtils#extractNetworkPortion} before calling this method.
*
* <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 destAddress the destination address to test for possible short code
* @param countryIso the ISO country code
*
* @return
* {@link SmsManager#SMS_CATEGORY_NOT_SHORT_CODE},
* {@link SmsManager#SMS_CATEGORY_FREE_SHORT_CODE},
* {@link SmsManager#SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE},
* {@link SmsManager#SMS_CATEGORY_PREMIUM_SHORT_CODE}, or
* {@link SmsManager#SMS_CATEGORY_STANDARD_SHORT_CODE}
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@TestApi
public @SmsShortCodeCategory int checkSmsShortCodeDestination(
String destAddress, String countryIso) {
try {
ISms iccISms = getISmsServiceOrThrow();
if (iccISms != null) {
return iccISms.checkSmsShortCodeDestination(getSubscriptionId(),
ActivityThread.currentPackageName(), destAddress, countryIso);
}
} catch (RemoteException e) {
Log.e(TAG, "checkSmsShortCodeDestination() RemoteException", e);
}
return SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
}
}