1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.telephony;
18
19import android.app.PendingIntent;
20import android.os.RemoteException;
21import android.os.ServiceManager;
22import android.text.TextUtils;
23
24import com.android.internal.telephony.ISms;
25import com.android.internal.telephony.IccConstants;
26import com.android.internal.telephony.SmsRawData;
27
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.List;
31
32/*
33 * TODO(code review): Curious question... Why are a lot of these
34 * methods not declared as static, since they do not seem to require
35 * any local object state?  Presumably this cannot be changed without
36 * interfering with the API...
37 */
38
39/**
40 * Manages SMS operations such as sending data, text, and pdu SMS messages.
41 * Get this object by calling the static method SmsManager.getDefault().
42 */
43public final class SmsManager {
44    /** Singleton object constructed during class initialization. */
45    private static final SmsManager sInstance = new SmsManager();
46
47    /**
48     * Send a text based SMS.
49     *
50     * @param destinationAddress the address to send the message to
51     * @param scAddress is the service center address or null to use
52     *  the current default SMSC
53     * @param text the body of the message to send
54     * @param sentIntent if not NULL this <code>PendingIntent</code> is
55     *  broadcast when the message is successfully sent, or failed.
56     *  The result code will be <code>Activity.RESULT_OK</code> for success,
57     *  or one of these errors:<br>
58     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
59     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
60     *  <code>RESULT_ERROR_NULL_PDU</code><br>
61     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
62     *  the extra "errorCode" containing a radio technology specific value,
63     *  generally only useful for troubleshooting.<br>
64     *  The per-application based SMS control checks sentIntent. If sentIntent
65     *  is NULL the caller will be checked against all unknown applications,
66     *  which cause smaller number of SMS to be sent in checking period.
67     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
68     *  broadcast when the message is delivered to the recipient.  The
69     *  raw pdu of the status report is in the extended data ("pdu").
70     *
71     * @throws IllegalArgumentException if destinationAddress or text are empty
72     */
73    public void sendTextMessage(
74            String destinationAddress, String scAddress, String text,
75            PendingIntent sentIntent, PendingIntent deliveryIntent) {
76        if (TextUtils.isEmpty(destinationAddress)) {
77            throw new IllegalArgumentException("Invalid destinationAddress");
78        }
79
80        if (TextUtils.isEmpty(text)) {
81            throw new IllegalArgumentException("Invalid message body");
82        }
83
84        try {
85            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
86            if (iccISms != null) {
87                iccISms.sendText(destinationAddress, scAddress, text, sentIntent, deliveryIntent);
88            }
89        } catch (RemoteException ex) {
90            // ignore it
91        }
92    }
93
94    /**
95     * Divide a message text into several fragments, none bigger than
96     * the maximum SMS message size.
97     *
98     * @param text the original message.  Must not be null.
99     * @return an <code>ArrayList</code> of strings that, in order,
100     *   comprise the original message
101     */
102    public ArrayList<String> divideMessage(String text) {
103        return SmsMessage.fragmentText(text);
104    }
105
106    /**
107     * Send a multi-part text based SMS.  The callee should have already
108     * divided the message into correctly sized parts by calling
109     * <code>divideMessage</code>.
110     *
111     * @param destinationAddress the address to send the message to
112     * @param scAddress is the service center address or null to use
113     *   the current default SMSC
114     * @param parts an <code>ArrayList</code> of strings that, in order,
115     *   comprise the original message
116     * @param sentIntents if not null, an <code>ArrayList</code> of
117     *   <code>PendingIntent</code>s (one for each message part) that is
118     *   broadcast when the corresponding message part has been sent.
119     *   The result code will be <code>Activity.RESULT_OK</code> for success,
120     *   or one of these errors:<br>
121     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
122     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
123     *   <code>RESULT_ERROR_NULL_PDU</code><br>
124     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
125     *   the extra "errorCode" containing a radio technology specific value,
126     *   generally only useful for troubleshooting.<br>
127     *   The per-application based SMS control checks sentIntent. If sentIntent
128     *   is NULL the caller will be checked against all unknown applications,
129     *   which cause smaller number of SMS to be sent in checking period.
130     * @param deliveryIntents if not null, an <code>ArrayList</code> of
131     *   <code>PendingIntent</code>s (one for each message part) that is
132     *   broadcast when the corresponding message part has been delivered
133     *   to the recipient.  The raw pdu of the status report is in the
134     *   extended data ("pdu").
135     *
136     * @throws IllegalArgumentException if destinationAddress or data are empty
137     */
138    public void sendMultipartTextMessage(
139            String destinationAddress, String scAddress, ArrayList<String> parts,
140            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
141        if (TextUtils.isEmpty(destinationAddress)) {
142            throw new IllegalArgumentException("Invalid destinationAddress");
143        }
144        if (parts == null || parts.size() < 1) {
145            throw new IllegalArgumentException("Invalid message body");
146        }
147
148        if (parts.size() > 1) {
149            try {
150                ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
151                if (iccISms != null) {
152                    iccISms.sendMultipartText(destinationAddress, scAddress, parts,
153                            sentIntents, deliveryIntents);
154                }
155            } catch (RemoteException ex) {
156                // ignore it
157            }
158        } else {
159            PendingIntent sentIntent = null;
160            PendingIntent deliveryIntent = null;
161            if (sentIntents != null && sentIntents.size() > 0) {
162                sentIntent = sentIntents.get(0);
163            }
164            if (deliveryIntents != null && deliveryIntents.size() > 0) {
165                deliveryIntent = deliveryIntents.get(0);
166            }
167            sendTextMessage(destinationAddress, scAddress, parts.get(0),
168                    sentIntent, deliveryIntent);
169        }
170    }
171
172    /**
173     * Send a data based SMS to a specific application port.
174     *
175     * @param destinationAddress the address to send the message to
176     * @param scAddress is the service center address or null to use
177     *  the current default SMSC
178     * @param destinationPort the port to deliver the message to
179     * @param data the body of the message to send
180     * @param sentIntent if not NULL this <code>PendingIntent</code> is
181     *  broadcast when the message is successfully sent, or failed.
182     *  The result code will be <code>Activity.RESULT_OK</code> for success,
183     *  or one of these errors:<br>
184     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
185     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
186     *  <code>RESULT_ERROR_NULL_PDU</code><br>
187     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
188     *  the extra "errorCode" containing a radio technology specific value,
189     *  generally only useful for troubleshooting.<br>
190     *  The per-application based SMS control checks sentIntent. If sentIntent
191     *  is NULL the caller will be checked against all unknown applications,
192     *  which cause smaller number of SMS to be sent in checking period.
193     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
194     *  broadcast when the message is delivered to the recipient.  The
195     *  raw pdu of the status report is in the extended data ("pdu").
196     *
197     * @throws IllegalArgumentException if destinationAddress or data are empty
198     */
199    public void sendDataMessage(
200            String destinationAddress, String scAddress, short destinationPort,
201            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
202        if (TextUtils.isEmpty(destinationAddress)) {
203            throw new IllegalArgumentException("Invalid destinationAddress");
204        }
205
206        if (data == null || data.length == 0) {
207            throw new IllegalArgumentException("Invalid message data");
208        }
209
210        try {
211            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
212            if (iccISms != null) {
213                iccISms.sendData(destinationAddress, scAddress, destinationPort & 0xFFFF,
214                        data, sentIntent, deliveryIntent);
215            }
216        } catch (RemoteException ex) {
217            // ignore it
218        }
219    }
220
221    /**
222     * Get the default instance of the SmsManager
223     *
224     * @return the default instance of the SmsManager
225     */
226    public static SmsManager getDefault() {
227        return sInstance;
228    }
229
230    private SmsManager() {
231        //nothing
232    }
233
234    /**
235     * Copy a raw SMS PDU to the ICC.
236     * ICC (Integrated Circuit Card) is the card of the device.
237     * For example, this can be the SIM or USIM for GSM.
238     *
239     * @param smsc the SMSC for this message, or NULL for the default SMSC
240     * @param pdu the raw PDU to store
241     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
242     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
243     * @return true for success
244     *
245     * {@hide}
246     */
247    public boolean copyMessageToIcc(byte[] smsc, byte[] pdu, int status) {
248        boolean success = false;
249
250        try {
251            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
252            if (iccISms != null) {
253                success = iccISms.copyMessageToIccEf(status, pdu, smsc);
254            }
255        } catch (RemoteException ex) {
256            // ignore it
257        }
258
259        return success;
260    }
261
262    /**
263     * Delete the specified message from the ICC.
264     * ICC (Integrated Circuit Card) is the card of the device.
265     * For example, this can be the SIM or USIM for GSM.
266     *
267     * @param messageIndex is the record index of the message on ICC
268     * @return true for success
269     *
270     * {@hide}
271     */
272    public boolean
273    deleteMessageFromIcc(int messageIndex) {
274        boolean success = false;
275        byte[] pdu = new byte[IccConstants.SMS_RECORD_LENGTH-1];
276        Arrays.fill(pdu, (byte)0xff);
277
278        try {
279            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
280            if (iccISms != null) {
281                success = iccISms.updateMessageOnIccEf(messageIndex, STATUS_ON_ICC_FREE, pdu);
282            }
283        } catch (RemoteException ex) {
284            // ignore it
285        }
286
287        return success;
288    }
289
290    /**
291     * Update the specified message on the ICC.
292     * ICC (Integrated Circuit Card) is the card of the device.
293     * For example, this can be the SIM or USIM for GSM.
294     *
295     * @param messageIndex record index of message to update
296     * @param newStatus new message status (STATUS_ON_ICC_READ,
297     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
298     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
299     * @param pdu the raw PDU to store
300     * @return true for success
301     *
302     * {@hide}
303     */
304    public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
305        boolean success = false;
306
307        try {
308            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
309            if (iccISms != null) {
310                success = iccISms.updateMessageOnIccEf(messageIndex, newStatus, pdu);
311            }
312        } catch (RemoteException ex) {
313            // ignore it
314        }
315
316        return success;
317    }
318
319    /**
320     * Retrieves all messages currently stored on ICC.
321     * ICC (Integrated Circuit Card) is the card of the device.
322     * For example, this can be the SIM or USIM for GSM.
323     *
324     * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
325     *
326     * {@hide}
327     */
328    public static ArrayList<SmsMessage> getAllMessagesFromIcc() {
329        List<SmsRawData> records = null;
330
331        try {
332            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
333            if (iccISms != null) {
334                records = iccISms.getAllMessagesFromIccEf();
335            }
336        } catch (RemoteException ex) {
337            // ignore it
338        }
339
340        return createMessageListFromRawRecords(records);
341    }
342
343    /**
344     * Enable reception of cell broadcast (SMS-CB) messages with the given
345     * message identifier. Note that if two different clients enable the same
346     * message identifier, they must both disable it for the device to stop
347     * receiving those messages. All received messages will be broadcast in an
348     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
349     * Note: This call is blocking, callers may want to avoid calling it from
350     * the main thread of an application.
351     *
352     * @param messageIdentifier Message identifier as specified in TS 23.041
353     * @return true if successful, false otherwise
354     * @see #disableCellBroadcast(int)
355     *
356     * {@hide}
357     */
358    public boolean enableCellBroadcast(int messageIdentifier) {
359        boolean success = false;
360
361        try {
362            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
363            if (iccISms != null) {
364                success = iccISms.enableCellBroadcast(messageIdentifier);
365            }
366        } catch (RemoteException ex) {
367            // ignore it
368        }
369
370        return success;
371    }
372
373    /**
374     * Disable reception of cell broadcast (SMS-CB) messages with the given
375     * message identifier. Note that if two different clients enable the same
376     * message identifier, they must both disable it for the device to stop
377     * receiving those messages.
378     * Note: This call is blocking, callers may want to avoid calling it from
379     * the main thread of an application.
380     *
381     * @param messageIdentifier Message identifier as specified in TS 23.041
382     * @return true if successful, false otherwise
383     *
384     * @see #enableCellBroadcast(int)
385     *
386     * {@hide}
387     */
388    public boolean disableCellBroadcast(int messageIdentifier) {
389        boolean success = false;
390
391        try {
392            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
393            if (iccISms != null) {
394                success = iccISms.disableCellBroadcast(messageIdentifier);
395            }
396        } catch (RemoteException ex) {
397            // ignore it
398        }
399
400        return success;
401    }
402
403    /**
404     * Enable reception of cell broadcast (SMS-CB) messages with the given
405     * message identifier range. Note that if two different clients enable the same
406     * message identifier, they must both disable it for the device to stop
407     * receiving those messages. All received messages will be broadcast in an
408     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
409     * Note: This call is blocking, callers may want to avoid calling it from
410     * the main thread of an application.
411     *
412     * @param startMessageId first message identifier as specified in TS 23.041
413     * @param endMessageId last message identifier as specified in TS 23.041
414     * @return true if successful, false otherwise
415     * @see #disableCellBroadcastRange(int, int)
416     *
417     * {@hide}
418     */
419    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
420        boolean success = false;
421
422        try {
423            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
424            if (iccISms != null) {
425                success = iccISms.enableCellBroadcastRange(startMessageId, endMessageId);
426            }
427        } catch (RemoteException ex) {
428            // ignore it
429        }
430
431        return success;
432    }
433
434    /**
435     * Disable reception of cell broadcast (SMS-CB) messages with the given
436     * message identifier range. Note that if two different clients enable the same
437     * message identifier, they must both disable it for the device to stop
438     * receiving those messages.
439     * Note: This call is blocking, callers may want to avoid calling it from
440     * the main thread of an application.
441     *
442     * @param startMessageId first message identifier as specified in TS 23.041
443     * @param endMessageId last message identifier as specified in TS 23.041
444     * @return true if successful, false otherwise
445     *
446     * @see #enableCellBroadcastRange(int, int)
447     *
448     * {@hide}
449     */
450    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
451        boolean success = false;
452
453        try {
454            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
455            if (iccISms != null) {
456                success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId);
457            }
458        } catch (RemoteException ex) {
459            // ignore it
460        }
461
462        return success;
463    }
464
465    /**
466     * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
467     * records returned by <code>getAllMessagesFromIcc()</code>
468     *
469     * @param records SMS EF records, returned by
470     *   <code>getAllMessagesFromIcc</code>
471     * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
472     */
473    private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
474        ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
475        if (records != null) {
476            int count = records.size();
477            for (int i = 0; i < count; i++) {
478                SmsRawData data = records.get(i);
479                // List contains all records, including "free" records (null)
480                if (data != null) {
481                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
482                    if (sms != null) {
483                        messages.add(sms);
484                    }
485                }
486            }
487        }
488        return messages;
489    }
490
491    // see SmsMessage.getStatusOnIcc
492
493    /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
494    static public final int STATUS_ON_ICC_FREE      = 0;
495
496    /** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
497    static public final int STATUS_ON_ICC_READ      = 1;
498
499    /** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
500    static public final int STATUS_ON_ICC_UNREAD    = 3;
501
502    /** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
503    static public final int STATUS_ON_ICC_SENT      = 5;
504
505    /** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
506    static public final int STATUS_ON_ICC_UNSENT    = 7;
507
508    // SMS send failure result codes
509
510    /** Generic failure cause */
511    static public final int RESULT_ERROR_GENERIC_FAILURE    = 1;
512    /** Failed because radio was explicitly turned off */
513    static public final int RESULT_ERROR_RADIO_OFF          = 2;
514    /** Failed because no pdu provided */
515    static public final int RESULT_ERROR_NULL_PDU           = 3;
516    /** Failed because service is currently unavailable */
517    static public final int RESULT_ERROR_NO_SERVICE         = 4;
518    /** Failed because we reached the sending queue limit.  {@hide} */
519    static public final int RESULT_ERROR_LIMIT_EXCEEDED     = 5;
520    /** Failed because FDN is enabled. {@hide} */
521    static public final int RESULT_ERROR_FDN_CHECK_FAILURE  = 6;
522}
523