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