/* * 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.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 {@link #getDefault()}. * *

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}. */ public final class SmsManager { /** Singleton object constructed during class initialization. */ private static final SmsManager sInstance = new SmsManager(); /** * Send a text based SMS. * *

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

* *

Note: Beginning with Android 4.4 (API level 19), if * and only if 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}.

* * * @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 = getISmsServiceOrThrow(); 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. * *

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

* *

Note: Beginning with Android 4.4 (API level 19), if * and only if 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}.

* * @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 * PendingIntents (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 * PendingIntents (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 = getISmsServiceOrThrow(); 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. * *

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

* * @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 = getISmsServiceOrThrow(); 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 } /** * Returns the ISms service, or throws an UnsupportedOperationException if * the service does not exist. */ private static ISms getISmsServiceOrThrow() { ISms iccISms = getISmsService(); if (iccISms == null) { throw new UnsupportedOperationException("Sms is not supported"); } return iccISms; } 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. * * @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 = getISmsService(); 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 = getISmsService(); 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 = getISmsService(); 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 = getISmsService(); 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 = getISmsService(); 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 = getISmsService(); 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 = getISmsService(); 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 = getISmsService(); if (iccISms != null) { success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId); } } catch (RemoteException ex) { // ignore it } return success; } /** * Create a list of SmsMessages 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 = getISmsService(); 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 = getISmsService(); 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; }