/* * Copyright (C) 2007 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.gsm; import android.app.PendingIntent; import android.os.RemoteException; import android.os.IServiceManager; import android.os.ServiceManager; import android.os.ServiceManagerNative; import android.text.TextUtils; import com.android.internal.telephony.gsm.EncodeException; import com.android.internal.telephony.gsm.GsmAlphabet; import com.android.internal.telephony.gsm.ISms; import com.android.internal.telephony.gsm.SimConstants; import com.android.internal.telephony.gsm.SmsRawData; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 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 { private static SmsManager sInstance; /** * 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 <code>PendingIntent</code> is * broadcast when the message is sucessfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors: * <code>RESULT_ERROR_GENERIC_FAILURE</code> * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applicaitons, * 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) { if (TextUtils.isEmpty(destinationAddress)) { throw new IllegalArgumentException("Invalid destinationAddress"); } if (TextUtils.isEmpty(text)) { throw new IllegalArgumentException("Invalid message body"); } SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu( scAddress, destinationAddress, text, (deliveryIntent != null)); sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent); } /** * Divide a text message into several messages, 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 */ public ArrayList<String> divideMessage(String text) { int size = text.length(); int[] params = SmsMessage.calculateLength(text, false); /* SmsMessage.calculateLength returns an int[4] with: * int[0] being the number of SMS's required, * int[1] the number of code units used, * int[2] is the number of code units remaining until the next message. * int[3] is the encoding type that should be used for the message. */ int messageCount = params[0]; int encodingType = params[3]; ArrayList<String> result = new ArrayList<String>(messageCount); int start = 0; int limit; if (messageCount > 1) { limit = (encodingType == SmsMessage.ENCODING_7BIT) ? SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER : SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; } else { limit = (encodingType == SmsMessage.ENCODING_7BIT) ? SmsMessage.MAX_USER_DATA_SEPTETS : SmsMessage.MAX_USER_DATA_BYTES; } try { while (start < size) { int end = GsmAlphabet.findLimitIndex(text, start, limit, encodingType); result.add(text.substring(start, end)); start = end; } } catch (EncodeException e) { // ignore it. } return result; } /** * Send a multi-part text based SMS. The callee should have already * divided the message into correctly sized parts by calling * <code>divideMessage</code>. * * @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: * <code>RESULT_ERROR_GENERIC_FAILURE</code> * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applicaitons, * 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"). */ public void sendMultipartTextMessage( String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> 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 simISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (simISms != null) { simISms.sendMultipartText(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 <code>PendingIntent</code> is * broadcast when the message is sucessfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors: * <code>RESULT_ERROR_GENERIC_FAILURE</code> * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applicaitons, * 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"); } SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress, destinationPort, data, (deliveryIntent != null)); sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent); } /** * Send a raw SMS PDU. * * @param smsc the SMSC to send the message through, or NULL for the * default SMSC * @param pdu the raw PDU to send * @param sentIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is sucessfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors: * <code>RESULT_ERROR_GENERIC_FAILURE</code> * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applicaitons, * 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"). * */ private void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent, PendingIntent deliveryIntent) { try { ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (simISms != null) { simISms.sendRawPdu(smsc, pdu, 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() { if (sInstance == null) { sInstance = new SmsManager(); } return sInstance; } private SmsManager() { // nothing to see here } /** * Copy a raw SMS PDU to the SIM. * * @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_SIM_READ, STATUS_ON_SIM_UNREAD, * STATUS_ON_SIM_SENT, STATUS_ON_SIM_UNSENT) * @return true for success * * {@hide} */ public boolean copyMessageToSim(byte[] smsc, byte[] pdu, int status) { boolean success = false; try { ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (simISms != null) { success = simISms.copyMessageToSimEf(status, pdu, smsc); } } catch (RemoteException ex) { // ignore it } return success; } /** * Delete the specified message from the SIM. * * @param messageIndex is the record index of the message on SIM * @return true for success * * {@hide} */ public boolean deleteMessageFromSim(int messageIndex) { boolean success = false; byte[] pdu = new byte[SimConstants.SMS_RECORD_LENGTH-1]; Arrays.fill(pdu, (byte)0xff); try { ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (simISms != null) { success = simISms.updateMessageOnSimEf(messageIndex, STATUS_ON_SIM_FREE, pdu); } } catch (RemoteException ex) { // ignore it } return success; } /** * Update the specified message on the SIM. * * @param messageIndex record index of message to update * @param newStatus new message status (STATUS_ON_SIM_READ, * STATUS_ON_SIM_UNREAD, STATUS_ON_SIM_SENT, * STATUS_ON_SIM_UNSENT, STATUS_ON_SIM_FREE) * @param pdu the raw PDU to store * @return true for success * * {@hide} */ public boolean updateMessageOnSim(int messageIndex, int newStatus, byte[] pdu) { boolean success = false; try { ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (simISms != null) { success = simISms.updateMessageOnSimEf(messageIndex, newStatus, pdu); } } catch (RemoteException ex) { // ignore it } return success; } /** * Retrieves all messages currently stored on SIM. * * @return <code>ArrayList</code> of <code>SmsMessage</code> objects * * {@hide} */ public ArrayList<SmsMessage> getAllMessagesFromSim() { List<SmsRawData> records = null; try { ISms simISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); if (simISms != null) { records = simISms.getAllMessagesFromSimEf(); } } catch (RemoteException ex) { // ignore it } return createMessageListFromRawRecords(records); } /** * Create a list of <code>SmsMessage</code>s from a list of RawSmsData * records returned by <code>getAllMessagesFromSim()</code> * * @param records SMS EF records, returned by * <code>getAllMessagesFromSim</code> * @return <code>ArrayList</code> of <code>SmsMessage</code> objects. */ private ArrayList<SmsMessage> createMessageListFromRawRecords(List records) { ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>(); if (records != null) { int count = records.size(); for (int i = 0; i < count; i++) { SmsRawData data = (SmsRawData)records.get(i); // List contains all records, including "free" records (null) if (data != null) { SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes()); messages.add(sms); } } } return messages; } /** Free space (TS 51.011 10.5.3). */ static public final int STATUS_ON_SIM_FREE = 0; /** Received and read (TS 51.011 10.5.3). */ static public final int STATUS_ON_SIM_READ = 1; /** Received and unread (TS 51.011 10.5.3). */ static public final int STATUS_ON_SIM_UNREAD = 3; /** Stored and sent (TS 51.011 10.5.3). */ static public final int STATUS_ON_SIM_SENT = 5; /** Stored and unsent (TS 51.011 10.5.3). */ static public final int STATUS_ON_SIM_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; }