/*
* Copyright (C) 2008 The Android Open Source Project
* Copyright (c) 2012, The Linux Foundation. All rights reserved.
*
* 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.app.ActivityThread;
import android.app.PendingIntent;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import com.android.internal.telephony.ISms;
import com.android.internal.telephony.SmsRawData;
import com.android.internal.telephony.uicc.IccConstants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/*
* 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 SmsManager.getDefault().
*/
public final class SmsManager {
/** Singleton object constructed during class initialization. */
private static final SmsManager sInstance = new SmsManager();
/**
* Send a text based SMS.
*
* @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 PendingIntent
is
* broadcast when the message is successfully sent, or failed.
* The result code will be Activity.RESULT_OK
for success,
* or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE
the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.
* 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 PendingIntent
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) {
if (TextUtils.isEmpty(destinationAddress)) {
throw new IllegalArgumentException("Invalid destinationAddress");
}
if (TextUtils.isEmpty(text)) {
throw new IllegalArgumentException("Invalid message body");
}
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
iccISms.sendText(ActivityThread.currentPackageName(), destinationAddress,
scAddress, text, sentIntent, deliveryIntent);
}
} catch (RemoteException ex) {
// ignore it
}
}
/**
* 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 ArrayList
of strings that, in order,
* comprise the original message
*
* @throws IllegalArgumentException if text is null
*/
public ArrayList divideMessage(String text) {
if (null == text) {
throw new IllegalArgumentException("text is null");
}
return SmsMessage.fragmentText(text);
}
/**
* Send a multi-part text based SMS. The callee should have already
* divided the message into correctly sized parts by calling
* divideMessage
.
*
* @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 ArrayList
of strings that, in order,
* comprise the original message
* @param sentIntents if not null, an ArrayList
of
* PendingIntent
s (one for each message part) that is
* broadcast when the corresponding message part has been sent.
* The result code will be Activity.RESULT_OK
for success,
* or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE
each sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.
* 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 ArrayList
of
* PendingIntent
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 parts,
ArrayList sentIntents, ArrayList deliveryIntents) {
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) {
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
iccISms.sendMultipartText(ActivityThread.currentPackageName(),
destinationAddress, scAddress, parts,
sentIntents, deliveryIntents);
}
} catch (RemoteException ex) {
// ignore it
}
} 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);
}
sendTextMessage(destinationAddress, scAddress, parts.get(0),
sentIntent, deliveryIntent);
}
}
/**
* Send a data based SMS to a specific application port.
*
* @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 PendingIntent
is
* broadcast when the message is successfully sent, or failed.
* The result code will be Activity.RESULT_OK
for success,
* or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE
the sentIntent may include
* the extra "errorCode" containing a radio technology specific value,
* generally only useful for troubleshooting.
* 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 PendingIntent
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");
}
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
iccISms.sendData(ActivityThread.currentPackageName(),
destinationAddress, scAddress, destinationPort & 0xFFFF,
data, sentIntent, deliveryIntent);
}
} catch (RemoteException ex) {
// ignore it
}
}
/**
* Get the default instance of the SmsManager
*
* @return the default instance of the SmsManager
*/
public static SmsManager getDefault() {
return sInstance;
}
private SmsManager() {
//nothing
}
/**
* 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.
*
* @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}
*/
public boolean copyMessageToIcc(byte[] smsc, byte[] pdu, int status) {
boolean success = false;
if (null == pdu) {
throw new IllegalArgumentException("pdu is NULL");
}
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.copyMessageToIccEf(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.
*
* @param messageIndex is the record index of the message on ICC
* @return true for success
*
* {@hide}
*/
public boolean
deleteMessageFromIcc(int messageIndex) {
boolean success = false;
byte[] pdu = new byte[IccConstants.SMS_RECORD_LENGTH-1];
Arrays.fill(pdu, (byte)0xff);
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.updateMessageOnIccEf(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.
*
* @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}
*/
public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
boolean success = false;
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.updateMessageOnIccEf(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.
*
* @return ArrayList
of SmsMessage
objects
*
* {@hide}
*/
public static ArrayList getAllMessagesFromIcc() {
List records = null;
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
records = iccISms.getAllMessagesFromIccEf(ActivityThread.currentPackageName());
}
} catch (RemoteException ex) {
// ignore it
}
return createMessageListFromRawRecords(records);
}
/**
* Enable reception of cell broadcast (SMS-CB) messages with the given
* message identifier. 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.
*
* @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
* or C.R1001-G (3GPP2)
* @return true if successful, false otherwise
* @see #disableCellBroadcast(int)
*
* {@hide}
*/
public boolean enableCellBroadcast(int messageIdentifier) {
boolean success = false;
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.enableCellBroadcast(messageIdentifier);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Disable reception of cell broadcast (SMS-CB) messages with the given
* message identifier. 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.
*
* @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
* or C.R1001-G (3GPP2)
* @return true if successful, false otherwise
*
* @see #enableCellBroadcast(int)
*
* {@hide}
*/
public boolean disableCellBroadcast(int messageIdentifier) {
boolean success = false;
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.disableCellBroadcast(messageIdentifier);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Enable reception of cell broadcast (SMS-CB) messages with the given
* message identifier range. 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.
*
* @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)
* @return true if successful, false otherwise
* @see #disableCellBroadcastRange(int, int)
*
* @throws IllegalArgumentException if endMessageId < startMessageId
* {@hide}
*/
public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
boolean success = false;
if (endMessageId < startMessageId) {
throw new IllegalArgumentException("endMessageId < startMessageId");
}
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.enableCellBroadcastRange(startMessageId, endMessageId);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Disable reception of cell broadcast (SMS-CB) messages with the given
* message identifier range. 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.
*
* @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)
* @return true if successful, false otherwise
*
* @see #enableCellBroadcastRange(int, int)
*
* @throws IllegalArgumentException if endMessageId < startMessageId
* {@hide}
*/
public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
boolean success = false;
if (endMessageId < startMessageId) {
throw new IllegalArgumentException("endMessageId < startMessageId");
}
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId);
}
} catch (RemoteException ex) {
// ignore it
}
return success;
}
/**
* Create a list of SmsMessage
s from a list of RawSmsData
* records returned by getAllMessagesFromIcc()
*
* @param records SMS EF records, returned by
* getAllMessagesFromIcc
* @return ArrayList
of SmsMessage
objects.
*/
private static ArrayList createMessageListFromRawRecords(List records) {
ArrayList messages = new ArrayList();
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());
if (sms != null) {
messages.add(sms);
}
}
}
}
return messages;
}
/**
* SMS over IMS is supported if IMS is registered and SMS is supported
* on IMS.
*
* @return true if SMS over IMS is supported, false otherwise
*
* @see #getImsSmsFormat()
*
* @hide
*/
boolean isImsSmsSupported() {
boolean boSupported = false;
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
boSupported = iccISms.isImsSmsSupported();
}
} catch (RemoteException ex) {
// ignore it
}
return boSupported;
}
/**
* Gets SMS format supported on IMS. SMS over IMS format is
* either 3GPP or 3GPP2.
*
* @return SmsMessage.FORMAT_3GPP,
* SmsMessage.FORMAT_3GPP2
* or SmsMessage.FORMAT_UNKNOWN
*
* @see #isImsSmsSupported()
*
* @hide
*/
String getImsSmsFormat() {
String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
try {
ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
if (iccISms != null) {
format = iccISms.getImsSmsFormat();
}
} catch (RemoteException ex) {
// ignore it
}
return format;
}
// 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
/** 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. {@hide} */
static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5;
/** Failed because FDN is enabled. {@hide} */
static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6;
}