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