IccSmsInterfaceManager.java revision a2ce002a0327f0deb079cc6acad1c493e6ded442
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 com.android.internal.telephony;
18
19import android.Manifest;
20import android.app.AppOpsManager;
21import android.app.PendingIntent;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.database.Cursor;
25import android.database.sqlite.SQLiteException;
26import android.net.Uri;
27import android.os.AsyncResult;
28import android.os.Binder;
29import android.os.Handler;
30import android.os.Message;
31import android.os.UserManager;
32import android.provider.Telephony;
33import android.telephony.Rlog;
34import android.telephony.SmsManager;
35import android.util.Log;
36
37import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
38import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
39import com.android.internal.telephony.uicc.IccConstants;
40import com.android.internal.telephony.uicc.IccFileHandler;
41import com.android.internal.telephony.uicc.UiccController;
42import com.android.internal.util.HexDump;
43
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.List;
47
48import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
49import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
50import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
51
52import android.telephony.TelephonyManager;
53
54/**
55 * IccSmsInterfaceManager to provide an inter-process communication to
56 * access Sms in Icc.
57 */
58public class IccSmsInterfaceManager {
59    static final String LOG_TAG = "IccSmsInterfaceManager";
60    static final boolean DBG = true;
61
62    protected final Object mLock = new Object();
63    protected boolean mSuccess;
64    private List<SmsRawData> mSms;
65
66    private CellBroadcastRangeManager mCellBroadcastRangeManager =
67            new CellBroadcastRangeManager();
68    private CdmaBroadcastRangeManager mCdmaBroadcastRangeManager =
69            new CdmaBroadcastRangeManager();
70
71    private static final int EVENT_LOAD_DONE = 1;
72    private static final int EVENT_UPDATE_DONE = 2;
73    protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
74    protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
75    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
76    private static final int SMS_CB_CODE_SCHEME_MAX = 255;
77
78    protected PhoneBase mPhone;
79    final protected Context mContext;
80    final protected AppOpsManager mAppOps;
81    final private UserManager mUserManager;
82    protected SMSDispatcher mDispatcher;
83
84    protected Handler mHandler = new Handler() {
85        @Override
86        public void handleMessage(Message msg) {
87            AsyncResult ar;
88
89            switch (msg.what) {
90                case EVENT_UPDATE_DONE:
91                    ar = (AsyncResult) msg.obj;
92                    synchronized (mLock) {
93                        mSuccess = (ar.exception == null);
94                        mLock.notifyAll();
95                    }
96                    break;
97                case EVENT_LOAD_DONE:
98                    ar = (AsyncResult)msg.obj;
99                    synchronized (mLock) {
100                        if (ar.exception == null) {
101                            mSms = buildValidRawData((ArrayList<byte[]>) ar.result);
102                            //Mark SMS as read after importing it from card.
103                            markMessagesAsRead((ArrayList<byte[]>) ar.result);
104                        } else {
105                            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
106                                log("Cannot load Sms records");
107                            }
108                            if (mSms != null)
109                                mSms.clear();
110                        }
111                        mLock.notifyAll();
112                    }
113                    break;
114                case EVENT_SET_BROADCAST_ACTIVATION_DONE:
115                case EVENT_SET_BROADCAST_CONFIG_DONE:
116                    ar = (AsyncResult) msg.obj;
117                    synchronized (mLock) {
118                        mSuccess = (ar.exception == null);
119                        mLock.notifyAll();
120                    }
121                    break;
122            }
123        }
124    };
125
126    protected IccSmsInterfaceManager(PhoneBase phone) {
127        mPhone = phone;
128        mContext = phone.getContext();
129        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
130        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
131        mDispatcher = new ImsSMSDispatcher(phone,
132                phone.mSmsStorageMonitor, phone.mSmsUsageMonitor);
133    }
134
135    protected void markMessagesAsRead(ArrayList<byte[]> messages) {
136        if (messages == null) {
137            return;
138        }
139
140        //IccFileHandler can be null, if icc card is absent.
141        IccFileHandler fh = mPhone.getIccFileHandler();
142        if (fh == null) {
143            //shouldn't really happen, as messages are marked as read, only
144            //after importing it from icc.
145            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
146                log("markMessagesAsRead - aborting, no icc card present.");
147            }
148            return;
149        }
150
151        int count = messages.size();
152
153        for (int i = 0; i < count; i++) {
154             byte[] ba = messages.get(i);
155             if (ba[0] == STATUS_ON_ICC_UNREAD) {
156                 int n = ba.length;
157                 byte[] nba = new byte[n - 1];
158                 System.arraycopy(ba, 1, nba, 0, n - 1);
159                 byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
160                 fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
161                 if (Rlog.isLoggable("SMS", Log.DEBUG)) {
162                     log("SMS " + (i + 1) + " marked as read");
163                 }
164             }
165        }
166    }
167
168    protected void updatePhoneObject(PhoneBase phone) {
169        mPhone = phone;
170        mDispatcher.updatePhoneObject(phone);
171    }
172
173    protected void enforceReceiveAndSend(String message) {
174        mContext.enforceCallingPermission(
175                Manifest.permission.RECEIVE_SMS, message);
176        mContext.enforceCallingPermission(
177                Manifest.permission.SEND_SMS, message);
178    }
179
180    /**
181     * Update the specified message on the Icc.
182     *
183     * @param index record index of message to update
184     * @param status new message status (STATUS_ON_ICC_READ,
185     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
186     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
187     * @param pdu the raw PDU to store
188     * @return success or not
189     *
190     */
191
192    public boolean
193    updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
194        if (DBG) log("updateMessageOnIccEf: index=" + index +
195                " status=" + status + " ==> " +
196                "("+ Arrays.toString(pdu) + ")");
197        enforceReceiveAndSend("Updating message on Icc");
198        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
199                callingPackage) != AppOpsManager.MODE_ALLOWED) {
200            return false;
201        }
202        synchronized(mLock) {
203            mSuccess = false;
204            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
205
206            if (status == STATUS_ON_ICC_FREE) {
207                // RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM
208                // Special case FREE: call deleteSmsOnSim/Ruim instead of
209                // manipulating the record
210                // Will eventually fail if icc card is not present.
211                if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
212                    mPhone.mCi.deleteSmsOnSim(index, response);
213                } else {
214                    mPhone.mCi.deleteSmsOnRuim(index, response);
215                }
216            } else {
217                //IccFilehandler can be null if ICC card is not present.
218                IccFileHandler fh = mPhone.getIccFileHandler();
219                if (fh == null) {
220                    response.recycle();
221                    return mSuccess; /* is false */
222                }
223                byte[] record = makeSmsRecordData(status, pdu);
224                fh.updateEFLinearFixed(
225                        IccConstants.EF_SMS,
226                        index, record, null, response);
227            }
228            try {
229                mLock.wait();
230            } catch (InterruptedException e) {
231                log("interrupted while trying to update by index");
232            }
233        }
234        return mSuccess;
235    }
236
237    /**
238     * Copy a raw SMS PDU to the Icc.
239     *
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 success or not
244     *
245     */
246    public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
247        //NOTE smsc not used in RUIM
248        if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
249                "pdu=("+ Arrays.toString(pdu) +
250                "), smsc=(" + Arrays.toString(smsc) +")");
251        enforceReceiveAndSend("Copying message to Icc");
252        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
253                callingPackage) != AppOpsManager.MODE_ALLOWED) {
254            return false;
255        }
256        synchronized(mLock) {
257            mSuccess = false;
258            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
259
260            //RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM
261            if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
262                mPhone.mCi.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
263                        IccUtils.bytesToHexString(pdu), response);
264            } else {
265                mPhone.mCi.writeSmsToRuim(status, IccUtils.bytesToHexString(pdu),
266                        response);
267            }
268
269            try {
270                mLock.wait();
271            } catch (InterruptedException e) {
272                log("interrupted while trying to update by index");
273            }
274        }
275        return mSuccess;
276    }
277
278    /**
279     * Retrieves all messages currently stored on Icc.
280     *
281     * @return list of SmsRawData of all sms on Icc
282     */
283
284    public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
285        if (DBG) log("getAllMessagesFromEF");
286
287        mContext.enforceCallingOrSelfPermission(
288                Manifest.permission.RECEIVE_SMS,
289                "Reading messages from Icc");
290        if (mAppOps.noteOp(AppOpsManager.OP_READ_ICC_SMS, Binder.getCallingUid(),
291                callingPackage) != AppOpsManager.MODE_ALLOWED) {
292            return new ArrayList<SmsRawData>();
293        }
294        synchronized(mLock) {
295
296            IccFileHandler fh = mPhone.getIccFileHandler();
297            if (fh == null) {
298                Rlog.e(LOG_TAG, "Cannot load Sms records. No icc card?");
299                if (mSms != null) {
300                    mSms.clear();
301                    return mSms;
302                }
303            }
304
305            Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
306            fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response);
307
308            try {
309                mLock.wait();
310            } catch (InterruptedException e) {
311                log("interrupted while trying to load from the Icc");
312            }
313        }
314        return mSms;
315    }
316
317    /**
318     * Send a data based SMS to a specific application port.
319     *
320     * @param destAddr the address to send the message to
321     * @param scAddr is the service center address or null to use
322     *  the current default SMSC
323     * @param destPort the port to deliver the message to
324     * @param data the body of the message to send
325     * @param sentIntent if not NULL this <code>PendingIntent</code> is
326     *  broadcast when the message is successfully sent, or failed.
327     *  The result code will be <code>Activity.RESULT_OK<code> for success,
328     *  or one of these errors:<br>
329     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
330     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
331     *  <code>RESULT_ERROR_NULL_PDU</code><br>
332     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
333     *  the extra "errorCode" containing a radio technology specific value,
334     *  generally only useful for troubleshooting.<br>
335     *  The per-application based SMS control checks sentIntent. If sentIntent
336     *  is NULL the caller will be checked against all unknown applications,
337     *  which cause smaller number of SMS to be sent in checking period.
338     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
339     *  broadcast when the message is delivered to the recipient.  The
340     *  raw pdu of the status report is in the extended data ("pdu").
341     */
342
343    public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
344            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
345        mPhone.getContext().enforceCallingPermission(
346                Manifest.permission.SEND_SMS,
347                "Sending SMS message");
348        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
349            log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" +
350                destPort + " data='"+ HexDump.toHexString(data)  + "' sentIntent=" +
351                sentIntent + " deliveryIntent=" + deliveryIntent);
352        }
353        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
354                callingPackage) != AppOpsManager.MODE_ALLOWED) {
355            return;
356        }
357        mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
358    }
359
360    /**
361     * Send a text based SMS.
362     *
363     * @param destAddr the address to send the message to
364     * @param scAddr is the service center address or null to use
365     *  the current default SMSC
366     * @param text the body of the message to send
367     * @param sentIntent if not NULL this <code>PendingIntent</code> is
368     *  broadcast when the message is successfully sent, or failed.
369     *  The result code will be <code>Activity.RESULT_OK<code> for success,
370     *  or one of these errors:<br>
371     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
372     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
373     *  <code>RESULT_ERROR_NULL_PDU</code><br>
374     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
375     *  the extra "errorCode" containing a radio technology specific value,
376     *  generally only useful for troubleshooting.<br>
377     *  The per-application based SMS control checks sentIntent. If sentIntent
378     *  is NULL the caller will be checked against all unknown applications,
379     *  which cause smaller number of SMS to be sent in checking period.
380     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
381     *  broadcast when the message is delivered to the recipient.  The
382     *  raw pdu of the status report is in the extended data ("pdu").
383     */
384
385    public void sendText(String callingPackage, String destAddr, String scAddr,
386            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
387        mPhone.getContext().enforceCallingPermission(
388                Manifest.permission.SEND_SMS,
389                "Sending SMS message");
390        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
391            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
392                " text='"+ text + "' sentIntent=" +
393                sentIntent + " deliveryIntent=" + deliveryIntent);
394        }
395        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
396                callingPackage) != AppOpsManager.MODE_ALLOWED) {
397            return;
398        }
399        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
400                null/*messageUri*/, callingPackage);
401    }
402
403    /**
404     * Inject an SMS PDU into the android application framework.
405     *
406     * @param pdu is the byte array of pdu to be injected into android application framework
407     * @param format is the format of SMS pdu (3gpp or 3gpp2)
408     * @param receivedIntent if not NULL this <code>PendingIntent</code> is
409     *  broadcast when the message is successfully received by the
410     *  android application framework. This intent is broadcasted at
411     *  the same time an SMS received from radio is acknowledged back.
412     */
413    public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
414        enforceCarrierPrivilege();
415        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
416            log("pdu: " + pdu +
417                "\n format=" + format +
418                "\n receivedIntent=" + receivedIntent);
419        }
420        mDispatcher.injectSmsPdu(pdu, format, receivedIntent);
421    }
422
423    /**
424     * Update the status of a pending (send-by-IP) SMS message and resend by PSTN if necessary.
425     * This outbound message was handled by the carrier app. If the carrier app fails to send
426     * this message, it would be resent by PSTN.
427     *
428     * @param messageRef the reference number of the SMS message.
429     * @param success True if and only if the message was sent successfully. If its value is
430     *  false, this message should be resent via PSTN.
431     * {@hide}
432     */
433    public void updateSmsSendStatus(int messageRef, boolean success) {
434        enforceCarrierPrivilege();
435        mDispatcher.updateSmsSendStatus(messageRef, success);
436    }
437
438    /**
439     * Send a multi-part text based SMS.
440     *
441     * @param destAddr the address to send the message to
442     * @param scAddr is the service center address or null to use
443     *   the current default SMSC
444     * @param parts an <code>ArrayList</code> of strings that, in order,
445     *   comprise the original message
446     * @param sentIntents if not null, an <code>ArrayList</code> of
447     *   <code>PendingIntent</code>s (one for each message part) that is
448     *   broadcast when the corresponding message part has been sent.
449     *   The result code will be <code>Activity.RESULT_OK<code> for success,
450     *   or one of these errors:
451     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
452     *   <code>RESULT_ERROR_RADIO_OFF</code>
453     *   <code>RESULT_ERROR_NULL_PDU</code>.
454     *  The per-application based SMS control checks sentIntent. If sentIntent
455     *  is NULL the caller will be checked against all unknown applications,
456     *  which cause smaller number of SMS to be sent in checking period.
457     * @param deliveryIntents if not null, an <code>ArrayList</code> of
458     *   <code>PendingIntent</code>s (one for each message part) that is
459     *   broadcast when the corresponding message part has been delivered
460     *   to the recipient.  The raw pdu of the status report is in the
461     *   extended data ("pdu").
462     */
463
464    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
465            List<String> parts, List<PendingIntent> sentIntents,
466            List<PendingIntent> deliveryIntents) {
467        mPhone.getContext().enforceCallingPermission(
468                Manifest.permission.SEND_SMS,
469                "Sending SMS message");
470        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
471            int i = 0;
472            for (String part : parts) {
473                log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
474                        ", part[" + (i++) + "]=" + part);
475            }
476        }
477        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
478                callingPackage) != AppOpsManager.MODE_ALLOWED) {
479            return;
480        }
481        mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
482                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents,
483                null/*messageUri*/, callingPackage);
484    }
485
486
487    public int getPremiumSmsPermission(String packageName) {
488        return mDispatcher.getPremiumSmsPermission(packageName);
489    }
490
491
492    public void setPremiumSmsPermission(String packageName, int permission) {
493        mDispatcher.setPremiumSmsPermission(packageName, permission);
494    }
495
496    /**
497     * create SmsRawData lists from all sms record byte[]
498     * Use null to indicate "free" record
499     *
500     * @param messages List of message records from EF_SMS.
501     * @return SmsRawData list of all in-used records
502     */
503    protected ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
504        int count = messages.size();
505        ArrayList<SmsRawData> ret;
506
507        ret = new ArrayList<SmsRawData>(count);
508
509        for (int i = 0; i < count; i++) {
510            byte[] ba = messages.get(i);
511            if (ba[0] == STATUS_ON_ICC_FREE) {
512                ret.add(null);
513            } else {
514                ret.add(new SmsRawData(messages.get(i)));
515            }
516        }
517
518        return ret;
519    }
520
521    /**
522     * Generates an EF_SMS record from status and raw PDU.
523     *
524     * @param status Message status.  See TS 51.011 10.5.3.
525     * @param pdu Raw message PDU.
526     * @return byte array for the record.
527     */
528    protected byte[] makeSmsRecordData(int status, byte[] pdu) {
529        byte[] data;
530        if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
531            data = new byte[IccConstants.SMS_RECORD_LENGTH];
532        } else {
533            data = new byte[IccConstants.CDMA_SMS_RECORD_LENGTH];
534        }
535
536        // Status bits for this record.  See TS 51.011 10.5.3
537        data[0] = (byte)(status & 7);
538
539        System.arraycopy(pdu, 0, data, 1, pdu.length);
540
541        // Pad out with 0xFF's.
542        for (int j = pdu.length+1; j < data.length; j++) {
543            data[j] = -1;
544        }
545
546        return data;
547    }
548
549    public boolean enableCellBroadcast(int messageIdentifier) {
550        return enableCellBroadcastRange(messageIdentifier, messageIdentifier);
551    }
552
553    public boolean disableCellBroadcast(int messageIdentifier) {
554        return disableCellBroadcastRange(messageIdentifier, messageIdentifier);
555    }
556
557    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
558        if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
559            return enableGsmBroadcastRange(startMessageId, endMessageId);
560        } else {
561            return enableCdmaBroadcastRange(startMessageId, endMessageId);
562        }
563    }
564
565    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
566        if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
567            return disableGsmBroadcastRange(startMessageId, endMessageId);
568        } else {
569            return disableCdmaBroadcastRange(startMessageId, endMessageId);
570        }
571    }
572
573    synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) {
574        if (DBG) log("enableGsmBroadcastRange");
575
576        Context context = mPhone.getContext();
577
578        context.enforceCallingPermission(
579                "android.permission.RECEIVE_SMS",
580                "Enabling cell broadcast SMS");
581
582        String client = context.getPackageManager().getNameForUid(
583                Binder.getCallingUid());
584
585        if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
586            log("Failed to add cell broadcast subscription for MID range " + startMessageId
587                    + " to " + endMessageId + " from client " + client);
588            return false;
589        }
590
591        if (DBG)
592            log("Added cell broadcast subscription for MID range " + startMessageId
593                    + " to " + endMessageId + " from client " + client);
594
595        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
596
597        return true;
598    }
599
600    synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) {
601        if (DBG) log("disableGsmBroadcastRange");
602
603        Context context = mPhone.getContext();
604
605        context.enforceCallingPermission(
606                "android.permission.RECEIVE_SMS",
607                "Disabling cell broadcast SMS");
608
609        String client = context.getPackageManager().getNameForUid(
610                Binder.getCallingUid());
611
612        if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
613            log("Failed to remove cell broadcast subscription for MID range " + startMessageId
614                    + " to " + endMessageId + " from client " + client);
615            return false;
616        }
617
618        if (DBG)
619            log("Removed cell broadcast subscription for MID range " + startMessageId
620                    + " to " + endMessageId + " from client " + client);
621
622        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
623
624        return true;
625    }
626
627    synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) {
628        if (DBG) log("enableCdmaBroadcastRange");
629
630        Context context = mPhone.getContext();
631
632        context.enforceCallingPermission(
633                "android.permission.RECEIVE_SMS",
634                "Enabling cdma broadcast SMS");
635
636        String client = context.getPackageManager().getNameForUid(
637                Binder.getCallingUid());
638
639        if (!mCdmaBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
640            log("Failed to add cdma broadcast subscription for MID range " + startMessageId
641                    + " to " + endMessageId + " from client " + client);
642            return false;
643        }
644
645        if (DBG)
646            log("Added cdma broadcast subscription for MID range " + startMessageId
647                    + " to " + endMessageId + " from client " + client);
648
649        setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
650
651        return true;
652    }
653
654    synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) {
655        if (DBG) log("disableCdmaBroadcastRange");
656
657        Context context = mPhone.getContext();
658
659        context.enforceCallingPermission(
660                "android.permission.RECEIVE_SMS",
661                "Disabling cell broadcast SMS");
662
663        String client = context.getPackageManager().getNameForUid(
664                Binder.getCallingUid());
665
666        if (!mCdmaBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
667            log("Failed to remove cdma broadcast subscription for MID range " + startMessageId
668                    + " to " + endMessageId + " from client " + client);
669            return false;
670        }
671
672        if (DBG)
673            log("Removed cdma broadcast subscription for MID range " + startMessageId
674                    + " to " + endMessageId + " from client " + client);
675
676        setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
677
678        return true;
679    }
680
681    class CellBroadcastRangeManager extends IntRangeManager {
682        private ArrayList<SmsBroadcastConfigInfo> mConfigList =
683                new ArrayList<SmsBroadcastConfigInfo>();
684
685        /**
686         * Called when the list of enabled ranges has changed. This will be
687         * followed by zero or more calls to {@link #addRange} followed by
688         * a call to {@link #finishUpdate}.
689         */
690        protected void startUpdate() {
691            mConfigList.clear();
692        }
693
694        /**
695         * Called after {@link #startUpdate} to indicate a range of enabled
696         * values.
697         * @param startId the first id included in the range
698         * @param endId the last id included in the range
699         */
700        protected void addRange(int startId, int endId, boolean selected) {
701            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
702                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
703        }
704
705        /**
706         * Called to indicate the end of a range update started by the
707         * previous call to {@link #startUpdate}.
708         * @return true if successful, false otherwise
709         */
710        protected boolean finishUpdate() {
711            if (mConfigList.isEmpty()) {
712                return true;
713            } else {
714                SmsBroadcastConfigInfo[] configs =
715                        mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
716                return setCellBroadcastConfig(configs);
717            }
718        }
719    }
720
721    class CdmaBroadcastRangeManager extends IntRangeManager {
722        private ArrayList<CdmaSmsBroadcastConfigInfo> mConfigList =
723                new ArrayList<CdmaSmsBroadcastConfigInfo>();
724
725        /**
726         * Called when the list of enabled ranges has changed. This will be
727         * followed by zero or more calls to {@link #addRange} followed by a
728         * call to {@link #finishUpdate}.
729         */
730        protected void startUpdate() {
731            mConfigList.clear();
732        }
733
734        /**
735         * Called after {@link #startUpdate} to indicate a range of enabled
736         * values.
737         * @param startId the first id included in the range
738         * @param endId the last id included in the range
739         */
740        protected void addRange(int startId, int endId, boolean selected) {
741            mConfigList.add(new CdmaSmsBroadcastConfigInfo(startId, endId,
742                    1, selected));
743        }
744
745        /**
746         * Called to indicate the end of a range update started by the previous
747         * call to {@link #startUpdate}.
748         * @return true if successful, false otherwise
749         */
750        protected boolean finishUpdate() {
751            if (mConfigList.isEmpty()) {
752                return true;
753            } else {
754                CdmaSmsBroadcastConfigInfo[] configs =
755                        mConfigList.toArray(new CdmaSmsBroadcastConfigInfo[mConfigList.size()]);
756                return setCdmaBroadcastConfig(configs);
757            }
758        }
759    }
760
761    private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
762        if (DBG)
763            log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
764
765        synchronized (mLock) {
766            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
767
768            mSuccess = false;
769            mPhone.mCi.setGsmBroadcastConfig(configs, response);
770
771            try {
772                mLock.wait();
773            } catch (InterruptedException e) {
774                log("interrupted while trying to set cell broadcast config");
775            }
776        }
777
778        return mSuccess;
779    }
780
781    private boolean setCellBroadcastActivation(boolean activate) {
782        if (DBG)
783            log("Calling setCellBroadcastActivation(" + activate + ')');
784
785        synchronized (mLock) {
786            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
787
788            mSuccess = false;
789            mPhone.mCi.setGsmBroadcastActivation(activate, response);
790
791            try {
792                mLock.wait();
793            } catch (InterruptedException e) {
794                log("interrupted while trying to set cell broadcast activation");
795            }
796        }
797
798        return mSuccess;
799    }
800
801    private boolean setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs) {
802        if (DBG)
803            log("Calling setCdmaBroadcastConfig with " + configs.length + " configurations");
804
805        synchronized (mLock) {
806            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
807
808            mSuccess = false;
809            mPhone.mCi.setCdmaBroadcastConfig(configs, response);
810
811            try {
812                mLock.wait();
813            } catch (InterruptedException e) {
814                log("interrupted while trying to set cdma broadcast config");
815            }
816        }
817
818        return mSuccess;
819    }
820
821    private boolean setCdmaBroadcastActivation(boolean activate) {
822        if (DBG)
823            log("Calling setCdmaBroadcastActivation(" + activate + ")");
824
825        synchronized (mLock) {
826            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
827
828            mSuccess = false;
829            mPhone.mCi.setCdmaBroadcastActivation(activate, response);
830
831            try {
832                mLock.wait();
833            } catch (InterruptedException e) {
834                log("interrupted while trying to set cdma broadcast activation");
835            }
836        }
837
838        return mSuccess;
839    }
840
841    protected void log(String msg) {
842        Log.d(LOG_TAG, "[IccSmsInterfaceManager] " + msg);
843    }
844
845    public boolean isImsSmsSupported() {
846        return mDispatcher.isIms();
847    }
848
849    public String getImsSmsFormat() {
850        return mDispatcher.getImsSmsFormat();
851    }
852
853    public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
854            PendingIntent sentIntent, PendingIntent deliveryIntent) {
855        mPhone.getContext().enforceCallingPermission(Manifest.permission.SEND_SMS,
856                "Sending SMS message");
857        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
858            log("sendStoredText: scAddr=" + scAddress + " messageUri=" + messageUri
859                    + " sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent);
860        }
861        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg)
862                != AppOpsManager.MODE_ALLOWED) {
863            return;
864        }
865        final ContentResolver resolver = mPhone.getContext().getContentResolver();
866        if (!isFailedOrDraft(resolver, messageUri)) {
867            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredText: not FAILED or DRAFT message");
868            returnUnspecifiedFailure(sentIntent);
869            return;
870        }
871        final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
872        if (textAndAddress == null) {
873            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredText: can not load text");
874            returnUnspecifiedFailure(sentIntent);
875            return;
876        }
877        mDispatcher.sendText(textAndAddress[1], scAddress, textAndAddress[0],
878                sentIntent, deliveryIntent, messageUri, callingPkg);
879    }
880
881    public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
882            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
883        mPhone.getContext().enforceCallingPermission(Manifest.permission.SEND_SMS,
884                "Sending SMS message");
885        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg)
886                != AppOpsManager.MODE_ALLOWED) {
887            return;
888        }
889        final ContentResolver resolver = mPhone.getContext().getContentResolver();
890        if (!isFailedOrDraft(resolver, messageUri)) {
891            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: "
892                    + "not FAILED or DRAFT message");
893            returnUnspecifiedFailure(sentIntents);
894            return;
895        }
896        final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
897        if (textAndAddress == null) {
898            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: can not load text");
899            returnUnspecifiedFailure(sentIntents);
900            return;
901        }
902        final ArrayList<String> parts = SmsManager.getDefault().divideMessage(textAndAddress[0]);
903        if (parts == null || parts.size() < 1) {
904            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: can not divide text");
905            returnUnspecifiedFailure(sentIntents);
906            return;
907        }
908        mDispatcher.sendMultipartText(
909                textAndAddress[1], // destAddress
910                scAddress,
911                parts,
912                (ArrayList<PendingIntent>) sentIntents,
913                (ArrayList<PendingIntent>) deliveryIntents,
914                messageUri,
915                callingPkg);
916    }
917
918    private boolean isFailedOrDraft(ContentResolver resolver, Uri messageUri) {
919        // Clear the calling identity and query the database using the phone user id
920        // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
921        // between the calling uid and the package uid
922        final long identity = Binder.clearCallingIdentity();
923        Cursor cursor = null;
924        try {
925            cursor = resolver.query(
926                    messageUri,
927                    new String[]{ Telephony.Sms.TYPE },
928                    null/*selection*/,
929                    null/*selectionArgs*/,
930                    null/*sortOrder*/);
931            if (cursor != null && cursor.moveToFirst()) {
932                final int type = cursor.getInt(0);
933                return type == Telephony.Sms.MESSAGE_TYPE_DRAFT
934                        || type == Telephony.Sms.MESSAGE_TYPE_FAILED;
935            }
936        } catch (SQLiteException e) {
937            Log.e(LOG_TAG, "[IccSmsInterfaceManager]isFailedOrDraft: query message type failed", e);
938        } finally {
939            if (cursor != null) {
940                cursor.close();
941            }
942            Binder.restoreCallingIdentity(identity);
943        }
944        return false;
945    }
946
947    // Return an array including both the SMS text (0) and address (1)
948    private String[] loadTextAndAddress(ContentResolver resolver, Uri messageUri) {
949        // Clear the calling identity and query the database using the phone user id
950        // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
951        // between the calling uid and the package uid
952        final long identity = Binder.clearCallingIdentity();
953        Cursor cursor = null;
954        try {
955            cursor = resolver.query(
956                    messageUri,
957                    new String[]{
958                            Telephony.Sms.BODY,
959                            Telephony.Sms.ADDRESS
960                    },
961                    null/*selection*/,
962                    null/*selectionArgs*/,
963                    null/*sortOrder*/);
964            if (cursor != null && cursor.moveToFirst()) {
965                return new String[]{ cursor.getString(0), cursor.getString(1) };
966            }
967        } catch (SQLiteException e) {
968            Log.e(LOG_TAG, "[IccSmsInterfaceManager]loadText: query message text failed", e);
969        } finally {
970            if (cursor != null) {
971                cursor.close();
972            }
973            Binder.restoreCallingIdentity(identity);
974        }
975        return null;
976    }
977
978    private void returnUnspecifiedFailure(PendingIntent pi) {
979        if (pi != null) {
980            try {
981                pi.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
982            } catch (PendingIntent.CanceledException e) {
983                // ignore
984            }
985        }
986    }
987
988    private void returnUnspecifiedFailure(List<PendingIntent> pis) {
989        if (pis == null) {
990            return;
991        }
992        for (PendingIntent pi : pis) {
993            returnUnspecifiedFailure(pi);
994        }
995    }
996
997    private void enforceCarrierPrivilege() {
998        if (UiccController.getInstance().getUiccCard().getCarrierPrivilegeStatusForCurrentTransaction(
999                mContext.getPackageManager()) !=
1000                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
1001            throw new SecurityException("No Carrier Privilege.");
1002        }
1003    }
1004}
1005