IccSmsInterfaceManager.java revision ace9a749c5a2a5e07527f728b7331423d16c36cd
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.Context;
23import android.os.AsyncResult;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.Message;
27import android.os.ServiceManager;
28import android.telephony.Rlog;
29import android.util.Log;
30
31import com.android.internal.telephony.ISms;
32import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
33import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
34import com.android.internal.telephony.uicc.IccConstants;
35import com.android.internal.telephony.uicc.IccFileHandler;
36import com.android.internal.util.HexDump;
37
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.List;
41
42import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
43import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
44import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
45
46/**
47 * IccSmsInterfaceManager to provide an inter-process communication to
48 * access Sms in Icc.
49 */
50public class IccSmsInterfaceManager extends ISms.Stub {
51    static final String LOG_TAG = "IccSmsInterfaceManager";
52    static final boolean DBG = true;
53
54    protected final Object mLock = new Object();
55    protected boolean mSuccess;
56    private List<SmsRawData> mSms;
57
58    private CellBroadcastRangeManager mCellBroadcastRangeManager =
59            new CellBroadcastRangeManager();
60    private CdmaBroadcastRangeManager mCdmaBroadcastRangeManager =
61            new CdmaBroadcastRangeManager();
62
63    private static final int EVENT_LOAD_DONE = 1;
64    private static final int EVENT_UPDATE_DONE = 2;
65    protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
66    protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
67    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
68    private static final int SMS_CB_CODE_SCHEME_MAX = 255;
69
70    protected PhoneBase mPhone;
71    final protected Context mContext;
72    final protected AppOpsManager mAppOps;
73    protected SMSDispatcher mDispatcher;
74
75    protected Handler mHandler = new Handler() {
76        @Override
77        public void handleMessage(Message msg) {
78            AsyncResult ar;
79
80            switch (msg.what) {
81                case EVENT_UPDATE_DONE:
82                    ar = (AsyncResult) msg.obj;
83                    synchronized (mLock) {
84                        mSuccess = (ar.exception == null);
85                        mLock.notifyAll();
86                    }
87                    break;
88                case EVENT_LOAD_DONE:
89                    ar = (AsyncResult)msg.obj;
90                    synchronized (mLock) {
91                        if (ar.exception == null) {
92                            mSms = buildValidRawData((ArrayList<byte[]>) ar.result);
93                            //Mark SMS as read after importing it from card.
94                            markMessagesAsRead((ArrayList<byte[]>) ar.result);
95                        } else {
96                            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
97                                log("Cannot load Sms records");
98                            }
99                            if (mSms != null)
100                                mSms.clear();
101                        }
102                        mLock.notifyAll();
103                    }
104                    break;
105                case EVENT_SET_BROADCAST_ACTIVATION_DONE:
106                case EVENT_SET_BROADCAST_CONFIG_DONE:
107                    ar = (AsyncResult) msg.obj;
108                    synchronized (mLock) {
109                        mSuccess = (ar.exception == null);
110                        mLock.notifyAll();
111                    }
112                    break;
113            }
114        }
115    };
116
117    protected IccSmsInterfaceManager(PhoneBase phone) {
118        mPhone = phone;
119        mContext = phone.getContext();
120        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
121        mDispatcher = new ImsSMSDispatcher(phone,
122                phone.mSmsStorageMonitor, phone.mSmsUsageMonitor);
123        if (ServiceManager.getService("isms") == null) {
124            ServiceManager.addService("isms", this);
125        }
126    }
127
128    protected void markMessagesAsRead(ArrayList<byte[]> messages) {
129        if (messages == null) {
130            return;
131        }
132
133        //IccFileHandler can be null, if icc card is absent.
134        IccFileHandler fh = mPhone.getIccFileHandler();
135        if (fh == null) {
136            //shouldn't really happen, as messages are marked as read, only
137            //after importing it from icc.
138            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
139                log("markMessagesAsRead - aborting, no icc card present.");
140            }
141            return;
142        }
143
144        int count = messages.size();
145
146        for (int i = 0; i < count; i++) {
147             byte[] ba = messages.get(i);
148             if (ba[0] == STATUS_ON_ICC_UNREAD) {
149                 int n = ba.length;
150                 byte[] nba = new byte[n - 1];
151                 System.arraycopy(ba, 1, nba, 0, n - 1);
152                 byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
153                 fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
154                 if (Rlog.isLoggable("SMS", Log.DEBUG)) {
155                     log("SMS " + (i + 1) + " marked as read");
156                 }
157             }
158        }
159    }
160
161    protected void updatePhoneObject(PhoneBase phone) {
162        mPhone = phone;
163        mDispatcher.updatePhoneObject(phone);
164    }
165
166    protected void enforceReceiveAndSend(String message) {
167        mContext.enforceCallingPermission(
168                Manifest.permission.RECEIVE_SMS, message);
169        mContext.enforceCallingPermission(
170                Manifest.permission.SEND_SMS, message);
171    }
172
173    /**
174     * Update the specified message on the Icc.
175     *
176     * @param index record index of message to update
177     * @param status new message status (STATUS_ON_ICC_READ,
178     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
179     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
180     * @param pdu the raw PDU to store
181     * @return success or not
182     *
183     */
184    @Override
185    public boolean
186    updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
187        if (DBG) log("updateMessageOnIccEf: index=" + index +
188                " status=" + status + " ==> " +
189                "("+ Arrays.toString(pdu) + ")");
190        enforceReceiveAndSend("Updating message on Icc");
191        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
192                callingPackage) != AppOpsManager.MODE_ALLOWED) {
193            return false;
194        }
195        synchronized(mLock) {
196            mSuccess = false;
197            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
198
199            if (status == STATUS_ON_ICC_FREE) {
200                // RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM
201                // Special case FREE: call deleteSmsOnSim/Ruim instead of
202                // manipulating the record
203                // Will eventually fail if icc card is not present.
204                if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
205                    mPhone.mCi.deleteSmsOnSim(index, response);
206                } else {
207                    mPhone.mCi.deleteSmsOnRuim(index, response);
208                }
209            } else {
210                //IccFilehandler can be null if ICC card is not present.
211                IccFileHandler fh = mPhone.getIccFileHandler();
212                if (fh == null) {
213                    response.recycle();
214                    return mSuccess; /* is false */
215                }
216                byte[] record = makeSmsRecordData(status, pdu);
217                fh.updateEFLinearFixed(
218                        IccConstants.EF_SMS,
219                        index, record, null, response);
220            }
221            try {
222                mLock.wait();
223            } catch (InterruptedException e) {
224                log("interrupted while trying to update by index");
225            }
226        }
227        return mSuccess;
228    }
229
230    /**
231     * Copy a raw SMS PDU to the Icc.
232     *
233     * @param pdu the raw PDU to store
234     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
235     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
236     * @return success or not
237     *
238     */
239    @Override
240    public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
241        //NOTE smsc not used in RUIM
242        if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
243                "pdu=("+ Arrays.toString(pdu) +
244                "), smsc=(" + Arrays.toString(smsc) +")");
245        enforceReceiveAndSend("Copying message to Icc");
246        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
247                callingPackage) != AppOpsManager.MODE_ALLOWED) {
248            return false;
249        }
250        synchronized(mLock) {
251            mSuccess = false;
252            Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
253
254            //RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM
255            if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
256                mPhone.mCi.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
257                        IccUtils.bytesToHexString(pdu), response);
258            } else {
259                mPhone.mCi.writeSmsToRuim(status, IccUtils.bytesToHexString(pdu),
260                        response);
261            }
262
263            try {
264                mLock.wait();
265            } catch (InterruptedException e) {
266                log("interrupted while trying to update by index");
267            }
268        }
269        return mSuccess;
270    }
271
272    /**
273     * Retrieves all messages currently stored on Icc.
274     *
275     * @return list of SmsRawData of all sms on Icc
276     */
277    @Override
278    public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
279        if (DBG) log("getAllMessagesFromEF");
280
281        mContext.enforceCallingOrSelfPermission(
282                Manifest.permission.RECEIVE_SMS,
283                "Reading messages from Icc");
284        if (mAppOps.noteOp(AppOpsManager.OP_READ_ICC_SMS, Binder.getCallingUid(),
285                callingPackage) != AppOpsManager.MODE_ALLOWED) {
286            return new ArrayList<SmsRawData>();
287        }
288        synchronized(mLock) {
289
290            IccFileHandler fh = mPhone.getIccFileHandler();
291            if (fh == null) {
292                Rlog.e(LOG_TAG, "Cannot load Sms records. No icc card?");
293                if (mSms != null) {
294                    mSms.clear();
295                    return mSms;
296                }
297            }
298
299            Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
300            fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response);
301
302            try {
303                mLock.wait();
304            } catch (InterruptedException e) {
305                log("interrupted while trying to load from the Icc");
306            }
307        }
308        return mSms;
309    }
310
311    /**
312     * Send a data based SMS to a specific application port.
313     *
314     * @param destAddr the address to send the message to
315     * @param scAddr is the service center address or null to use
316     *  the current default SMSC
317     * @param destPort the port to deliver the message to
318     * @param data the body of the message to send
319     * @param sentIntent if not NULL this <code>PendingIntent</code> is
320     *  broadcast when the message is successfully sent, or failed.
321     *  The result code will be <code>Activity.RESULT_OK<code> for success,
322     *  or one of these errors:<br>
323     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
324     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
325     *  <code>RESULT_ERROR_NULL_PDU</code><br>
326     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
327     *  the extra "errorCode" containing a radio technology specific value,
328     *  generally only useful for troubleshooting.<br>
329     *  The per-application based SMS control checks sentIntent. If sentIntent
330     *  is NULL the caller will be checked against all unknown applications,
331     *  which cause smaller number of SMS to be sent in checking period.
332     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
333     *  broadcast when the message is delivered to the recipient.  The
334     *  raw pdu of the status report is in the extended data ("pdu").
335     */
336    @Override
337    public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
338            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
339        mPhone.getContext().enforceCallingPermission(
340                Manifest.permission.SEND_SMS,
341                "Sending SMS message");
342        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
343            log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" +
344                destPort + " data='"+ HexDump.toHexString(data)  + "' sentIntent=" +
345                sentIntent + " deliveryIntent=" + deliveryIntent);
346        }
347        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
348                callingPackage) != AppOpsManager.MODE_ALLOWED) {
349            return;
350        }
351        mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
352    }
353
354    /**
355     * Send a text based SMS.
356     *
357     * @param destAddr the address to send the message to
358     * @param scAddr is the service center address or null to use
359     *  the current default SMSC
360     * @param text the body of the message to send
361     * @param sentIntent if not NULL this <code>PendingIntent</code> is
362     *  broadcast when the message is successfully sent, or failed.
363     *  The result code will be <code>Activity.RESULT_OK<code> for success,
364     *  or one of these errors:<br>
365     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
366     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
367     *  <code>RESULT_ERROR_NULL_PDU</code><br>
368     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
369     *  the extra "errorCode" containing a radio technology specific value,
370     *  generally only useful for troubleshooting.<br>
371     *  The per-application based SMS control checks sentIntent. If sentIntent
372     *  is NULL the caller will be checked against all unknown applications,
373     *  which cause smaller number of SMS to be sent in checking period.
374     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
375     *  broadcast when the message is delivered to the recipient.  The
376     *  raw pdu of the status report is in the extended data ("pdu").
377     */
378    @Override
379    public void sendText(String callingPackage, String destAddr, String scAddr,
380            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
381        mPhone.getContext().enforceCallingPermission(
382                Manifest.permission.SEND_SMS,
383                "Sending SMS message");
384        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
385            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
386                " text='"+ text + "' sentIntent=" +
387                sentIntent + " deliveryIntent=" + deliveryIntent);
388        }
389        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
390                callingPackage) != AppOpsManager.MODE_ALLOWED) {
391            return;
392        }
393        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
394    }
395
396    /**
397     * Send a multi-part text based SMS.
398     *
399     * @param destAddr the address to send the message to
400     * @param scAddr is the service center address or null to use
401     *   the current default SMSC
402     * @param parts an <code>ArrayList</code> of strings that, in order,
403     *   comprise the original message
404     * @param sentIntents if not null, an <code>ArrayList</code> of
405     *   <code>PendingIntent</code>s (one for each message part) that is
406     *   broadcast when the corresponding message part has been sent.
407     *   The result code will be <code>Activity.RESULT_OK<code> for success,
408     *   or one of these errors:
409     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
410     *   <code>RESULT_ERROR_RADIO_OFF</code>
411     *   <code>RESULT_ERROR_NULL_PDU</code>.
412     *  The per-application based SMS control checks sentIntent. If sentIntent
413     *  is NULL the caller will be checked against all unknown applications,
414     *  which cause smaller number of SMS to be sent in checking period.
415     * @param deliveryIntents if not null, an <code>ArrayList</code> of
416     *   <code>PendingIntent</code>s (one for each message part) that is
417     *   broadcast when the corresponding message part has been delivered
418     *   to the recipient.  The raw pdu of the status report is in the
419     *   extended data ("pdu").
420     */
421    @Override
422    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
423            List<String> parts, List<PendingIntent> sentIntents,
424            List<PendingIntent> deliveryIntents) {
425        mPhone.getContext().enforceCallingPermission(
426                Manifest.permission.SEND_SMS,
427                "Sending SMS message");
428        if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
429            int i = 0;
430            for (String part : parts) {
431                log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
432                        ", part[" + (i++) + "]=" + part);
433            }
434        }
435        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
436                callingPackage) != AppOpsManager.MODE_ALLOWED) {
437            return;
438        }
439        mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
440                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents);
441    }
442
443    @Override
444    public int getPremiumSmsPermission(String packageName) {
445        return mDispatcher.getPremiumSmsPermission(packageName);
446    }
447
448    @Override
449    public void setPremiumSmsPermission(String packageName, int permission) {
450        mDispatcher.setPremiumSmsPermission(packageName, permission);
451    }
452
453    /**
454     * create SmsRawData lists from all sms record byte[]
455     * Use null to indicate "free" record
456     *
457     * @param messages List of message records from EF_SMS.
458     * @return SmsRawData list of all in-used records
459     */
460    protected ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
461        int count = messages.size();
462        ArrayList<SmsRawData> ret;
463
464        ret = new ArrayList<SmsRawData>(count);
465
466        for (int i = 0; i < count; i++) {
467            byte[] ba = messages.get(i);
468            if (ba[0] == STATUS_ON_ICC_FREE) {
469                ret.add(null);
470            } else {
471                ret.add(new SmsRawData(messages.get(i)));
472            }
473        }
474
475        return ret;
476    }
477
478    /**
479     * Generates an EF_SMS record from status and raw PDU.
480     *
481     * @param status Message status.  See TS 51.011 10.5.3.
482     * @param pdu Raw message PDU.
483     * @return byte array for the record.
484     */
485    protected byte[] makeSmsRecordData(int status, byte[] pdu) {
486        byte[] data = new byte[IccConstants.SMS_RECORD_LENGTH];
487
488        // Status bits for this record.  See TS 51.011 10.5.3
489        data[0] = (byte)(status & 7);
490
491        System.arraycopy(pdu, 0, data, 1, pdu.length);
492
493        // Pad out with 0xFF's.
494        for (int j = pdu.length+1; j < IccConstants.SMS_RECORD_LENGTH; j++) {
495            data[j] = -1;
496        }
497
498        return data;
499    }
500
501    public boolean enableCellBroadcast(int messageIdentifier) {
502        return enableCellBroadcastRange(messageIdentifier, messageIdentifier);
503    }
504
505    public boolean disableCellBroadcast(int messageIdentifier) {
506        return disableCellBroadcastRange(messageIdentifier, messageIdentifier);
507    }
508
509    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
510        if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
511            return enableGsmBroadcastRange(startMessageId, endMessageId);
512        } else {
513            return enableCdmaBroadcastRange(startMessageId, endMessageId);
514        }
515    }
516
517    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
518        if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
519            return disableGsmBroadcastRange(startMessageId, endMessageId);
520        } else {
521            return disableCdmaBroadcastRange(startMessageId, endMessageId);
522        }
523    }
524
525    synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) {
526        if (DBG) log("enableGsmBroadcastRange");
527
528        Context context = mPhone.getContext();
529
530        context.enforceCallingPermission(
531                "android.permission.RECEIVE_SMS",
532                "Enabling cell broadcast SMS");
533
534        String client = context.getPackageManager().getNameForUid(
535                Binder.getCallingUid());
536
537        if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
538            log("Failed to add cell broadcast subscription for MID range " + startMessageId
539                    + " to " + endMessageId + " from client " + client);
540            return false;
541        }
542
543        if (DBG)
544            log("Added cell broadcast subscription for MID range " + startMessageId
545                    + " to " + endMessageId + " from client " + client);
546
547        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
548
549        return true;
550    }
551
552    synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) {
553        if (DBG) log("disableGsmBroadcastRange");
554
555        Context context = mPhone.getContext();
556
557        context.enforceCallingPermission(
558                "android.permission.RECEIVE_SMS",
559                "Disabling cell broadcast SMS");
560
561        String client = context.getPackageManager().getNameForUid(
562                Binder.getCallingUid());
563
564        if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
565            log("Failed to remove cell broadcast subscription for MID range " + startMessageId
566                    + " to " + endMessageId + " from client " + client);
567            return false;
568        }
569
570        if (DBG)
571            log("Removed cell broadcast subscription for MID range " + startMessageId
572                    + " to " + endMessageId + " from client " + client);
573
574        setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
575
576        return true;
577    }
578
579    synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) {
580        if (DBG) log("enableCdmaBroadcastRange");
581
582        Context context = mPhone.getContext();
583
584        context.enforceCallingPermission(
585                "android.permission.RECEIVE_SMS",
586                "Enabling cdma broadcast SMS");
587
588        String client = context.getPackageManager().getNameForUid(
589                Binder.getCallingUid());
590
591        if (!mCdmaBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
592            log("Failed to add cdma broadcast subscription for MID range " + startMessageId
593                    + " to " + endMessageId + " from client " + client);
594            return false;
595        }
596
597        if (DBG)
598            log("Added cdma broadcast subscription for MID range " + startMessageId
599                    + " to " + endMessageId + " from client " + client);
600
601        setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
602
603        return true;
604    }
605
606    synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) {
607        if (DBG) log("disableCdmaBroadcastRange");
608
609        Context context = mPhone.getContext();
610
611        context.enforceCallingPermission(
612                "android.permission.RECEIVE_SMS",
613                "Disabling cell broadcast SMS");
614
615        String client = context.getPackageManager().getNameForUid(
616                Binder.getCallingUid());
617
618        if (!mCdmaBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
619            log("Failed to remove cdma broadcast subscription for MID range " + startMessageId
620                    + " to " + endMessageId + " from client " + client);
621            return false;
622        }
623
624        if (DBG)
625            log("Removed cdma broadcast subscription for MID range " + startMessageId
626                    + " to " + endMessageId + " from client " + client);
627
628        setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
629
630        return true;
631    }
632
633    class CellBroadcastRangeManager extends IntRangeManager {
634        private ArrayList<SmsBroadcastConfigInfo> mConfigList =
635                new ArrayList<SmsBroadcastConfigInfo>();
636
637        /**
638         * Called when the list of enabled ranges has changed. This will be
639         * followed by zero or more calls to {@link #addRange} followed by
640         * a call to {@link #finishUpdate}.
641         */
642        protected void startUpdate() {
643            mConfigList.clear();
644        }
645
646        /**
647         * Called after {@link #startUpdate} to indicate a range of enabled
648         * values.
649         * @param startId the first id included in the range
650         * @param endId the last id included in the range
651         */
652        protected void addRange(int startId, int endId, boolean selected) {
653            mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
654                        SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
655        }
656
657        /**
658         * Called to indicate the end of a range update started by the
659         * previous call to {@link #startUpdate}.
660         * @return true if successful, false otherwise
661         */
662        protected boolean finishUpdate() {
663            if (mConfigList.isEmpty()) {
664                return true;
665            } else {
666                SmsBroadcastConfigInfo[] configs =
667                        mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
668                return setCellBroadcastConfig(configs);
669            }
670        }
671    }
672
673    class CdmaBroadcastRangeManager extends IntRangeManager {
674        private ArrayList<CdmaSmsBroadcastConfigInfo> mConfigList =
675                new ArrayList<CdmaSmsBroadcastConfigInfo>();
676
677        /**
678         * Called when the list of enabled ranges has changed. This will be
679         * followed by zero or more calls to {@link #addRange} followed by a
680         * call to {@link #finishUpdate}.
681         */
682        protected void startUpdate() {
683            mConfigList.clear();
684        }
685
686        /**
687         * Called after {@link #startUpdate} to indicate a range of enabled
688         * values.
689         * @param startId the first id included in the range
690         * @param endId the last id included in the range
691         */
692        protected void addRange(int startId, int endId, boolean selected) {
693            mConfigList.add(new CdmaSmsBroadcastConfigInfo(startId, endId,
694                    1, selected));
695        }
696
697        /**
698         * Called to indicate the end of a range update started by the previous
699         * call to {@link #startUpdate}.
700         * @return true if successful, false otherwise
701         */
702        protected boolean finishUpdate() {
703            if (mConfigList.isEmpty()) {
704                return true;
705            } else {
706                CdmaSmsBroadcastConfigInfo[] configs =
707                        mConfigList.toArray(new CdmaSmsBroadcastConfigInfo[mConfigList.size()]);
708                return setCdmaBroadcastConfig(configs);
709            }
710        }
711    }
712
713    private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
714        if (DBG)
715            log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
716
717        synchronized (mLock) {
718            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
719
720            mSuccess = false;
721            mPhone.mCi.setGsmBroadcastConfig(configs, response);
722
723            try {
724                mLock.wait();
725            } catch (InterruptedException e) {
726                log("interrupted while trying to set cell broadcast config");
727            }
728        }
729
730        return mSuccess;
731    }
732
733    private boolean setCellBroadcastActivation(boolean activate) {
734        if (DBG)
735            log("Calling setCellBroadcastActivation(" + activate + ')');
736
737        synchronized (mLock) {
738            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
739
740            mSuccess = false;
741            mPhone.mCi.setGsmBroadcastActivation(activate, response);
742
743            try {
744                mLock.wait();
745            } catch (InterruptedException e) {
746                log("interrupted while trying to set cell broadcast activation");
747            }
748        }
749
750        return mSuccess;
751    }
752
753    private boolean setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs) {
754        if (DBG)
755            log("Calling setCdmaBroadcastConfig with " + configs.length + " configurations");
756
757        synchronized (mLock) {
758            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
759
760            mSuccess = false;
761            mPhone.mCi.setCdmaBroadcastConfig(configs, response);
762
763            try {
764                mLock.wait();
765            } catch (InterruptedException e) {
766                log("interrupted while trying to set cdma broadcast config");
767            }
768        }
769
770        return mSuccess;
771    }
772
773    private boolean setCdmaBroadcastActivation(boolean activate) {
774        if (DBG)
775            log("Calling setCdmaBroadcastActivation(" + activate + ")");
776
777        synchronized (mLock) {
778            Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
779
780            mSuccess = false;
781            mPhone.mCi.setCdmaBroadcastActivation(activate, response);
782
783            try {
784                mLock.wait();
785            } catch (InterruptedException e) {
786                log("interrupted while trying to set cdma broadcast activation");
787            }
788        }
789
790        return mSuccess;
791    }
792
793    protected void log(String msg) {
794        Log.d(LOG_TAG, "[IccSmsInterfaceManager] " + msg);
795    }
796
797    public boolean isImsSmsSupported() {
798        return mDispatcher.isIms();
799    }
800
801    public String getImsSmsFormat() {
802        return mDispatcher.getImsSmsFormat();
803    }
804}
805