GsmSMSDispatcher.java revision 850665a367489cce0b83431fa0e6e543b24062e0
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 * Copyright (c) 2012, The Linux Foundation. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.internal.telephony.gsm;
19
20import android.app.Activity;
21import android.app.PendingIntent;
22import android.app.PendingIntent.CanceledException;
23import android.content.Intent;
24import android.os.AsyncResult;
25import android.os.Message;
26import android.os.SystemProperties;
27import android.provider.Telephony.Sms;
28import android.provider.Telephony.Sms.Intents;
29import android.telephony.PhoneNumberUtils;
30import android.telephony.SmsCbLocation;
31import android.telephony.SmsCbMessage;
32import android.telephony.SmsManager;
33import android.telephony.gsm.GsmCellLocation;
34import android.telephony.Rlog;
35
36import com.android.internal.telephony.CommandsInterface;
37import com.android.internal.telephony.GsmAlphabet;
38import com.android.internal.telephony.PhoneBase;
39import com.android.internal.telephony.SmsConstants;
40import com.android.internal.telephony.SMSDispatcher;
41import com.android.internal.telephony.ImsSMSDispatcher;
42import com.android.internal.telephony.SmsHeader;
43import com.android.internal.telephony.SmsMessageBase;
44import com.android.internal.telephony.SmsStorageMonitor;
45import com.android.internal.telephony.SmsUsageMonitor;
46import com.android.internal.telephony.TelephonyProperties;
47import com.android.internal.telephony.uicc.IccRecords;
48import com.android.internal.telephony.uicc.IccUtils;
49import com.android.internal.telephony.uicc.UiccCardApplication;
50import com.android.internal.telephony.uicc.UiccController;
51import com.android.internal.telephony.uicc.UsimServiceTable;
52
53import java.util.HashMap;
54import java.util.Iterator;
55import java.util.concurrent.atomic.AtomicReference;
56
57public final class GsmSMSDispatcher extends SMSDispatcher {
58    private static final String TAG = "GsmSMSDispatcher";
59    private static final boolean VDBG = false;
60    private ImsSMSDispatcher mImsSMSDispatcher;
61    private UiccController mUiccController = null;
62    private AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
63    private AtomicReference<UiccCardApplication> mUiccApplication =
64            new AtomicReference<UiccCardApplication>();
65
66    /** Status report received */
67    private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
68
69    /** New broadcast SMS */
70    private static final int EVENT_NEW_BROADCAST_SMS = 101;
71
72    /** Result of writing SM to UICC (when SMS-PP service is not available). */
73    private static final int EVENT_WRITE_SMS_COMPLETE = 102;
74
75    /** Handler for SMS-PP data download messages to UICC. */
76    private final UsimDataDownloadHandler mDataDownloadHandler;
77
78    public GsmSMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
79            SmsUsageMonitor usageMonitor, ImsSMSDispatcher imsSMSDispatcher) {
80        super(phone, storageMonitor, usageMonitor);
81        mDataDownloadHandler = new UsimDataDownloadHandler(mCi);
82        mCi.setOnNewGsmSms(this, EVENT_NEW_SMS, null);
83        mCi.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
84        mCi.setOnNewGsmBroadcastSms(this, EVENT_NEW_BROADCAST_SMS, null);
85        mImsSMSDispatcher = imsSMSDispatcher;
86        mUiccController = UiccController.getInstance();
87        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
88        Rlog.d(TAG, "GsmSMSDispatcher created");
89    }
90
91    @Override
92    public void dispose() {
93        mCi.unSetOnNewGsmSms(this);
94        mCi.unSetOnSmsStatus(this);
95        mCi.unSetOnNewGsmBroadcastSms(this);
96    }
97
98    @Override
99    protected String getFormat() {
100        return SmsConstants.FORMAT_3GPP;
101    }
102
103    /**
104     * Handles 3GPP format-specific events coming from the phone stack.
105     * Other events are handled by {@link SMSDispatcher#handleMessage}.
106     *
107     * @param msg the message to handle
108     */
109    @Override
110    public void handleMessage(Message msg) {
111        switch (msg.what) {
112        case EVENT_NEW_SMS_STATUS_REPORT:
113            handleStatusReport((AsyncResult) msg.obj);
114            break;
115
116        case EVENT_NEW_BROADCAST_SMS:
117            handleBroadcastSms((AsyncResult)msg.obj);
118            break;
119
120        case EVENT_WRITE_SMS_COMPLETE:
121            AsyncResult ar = (AsyncResult) msg.obj;
122            if (ar.exception == null) {
123                Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
124                mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
125            } else {
126                Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
127                mCi.acknowledgeLastIncomingGsmSms(false,
128                        CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
129            }
130            break;
131
132        case EVENT_NEW_ICC_SMS:
133            ar = (AsyncResult)msg.obj;
134            dispatchMessage((SmsMessage)ar.result);
135            break;
136
137        case EVENT_ICC_CHANGED:
138            onUpdateIccAvailability();
139            break;
140
141        default:
142            super.handleMessage(msg);
143        }
144    }
145
146    /**
147     * Called when a status report is received.  This should correspond to
148     * a previously successful SEND.
149     *
150     * @param ar AsyncResult passed into the message handler.  ar.result should
151     *           be a String representing the status report PDU, as ASCII hex.
152     */
153    private void handleStatusReport(AsyncResult ar) {
154        String pduString = (String) ar.result;
155        SmsMessage sms = SmsMessage.newFromCDS(pduString);
156
157        if (sms != null) {
158            int tpStatus = sms.getStatus();
159            int messageRef = sms.mMessageRef;
160            for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
161                SmsTracker tracker = deliveryPendingList.get(i);
162                if (tracker.mMessageRef == messageRef) {
163                    // Found it.  Remove from list and broadcast.
164                    if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
165                       deliveryPendingList.remove(i);
166                    }
167                    PendingIntent intent = tracker.mDeliveryIntent;
168                    Intent fillIn = new Intent();
169                    fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString));
170                    fillIn.putExtra("format", getFormat());
171                    try {
172                        intent.send(mContext, Activity.RESULT_OK, fillIn);
173                    } catch (CanceledException ex) {}
174
175                    // Only expect to see one tracker matching this messageref
176                    break;
177                }
178            }
179        }
180        acknowledgeLastIncomingSms(true, Intents.RESULT_SMS_HANDLED, null);
181    }
182
183    /** {@inheritDoc} */
184    @Override
185    protected int dispatchMessage(SmsMessageBase smsb) {
186
187        // If sms is null, means there was a parsing error.
188        if (smsb == null) {
189            Rlog.e(TAG, "dispatchMessage: message is null");
190            return Intents.RESULT_SMS_GENERIC_ERROR;
191        }
192
193        SmsMessage sms = (SmsMessage) smsb;
194
195        if (sms.isTypeZero()) {
196            // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be
197            // Displayed/Stored/Notified. They should only be acknowledged.
198            Rlog.d(TAG, "Received short message type 0, Don't display or store it. Send Ack");
199            return Intents.RESULT_SMS_HANDLED;
200        }
201
202        // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
203        if (sms.isUsimDataDownload()) {
204            UsimServiceTable ust = mPhone.getUsimServiceTable();
205            // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
206            // assume that the data download service is not present. This is very unlikely to
207            // happen because the IMS connection will not be established until after the ISIM
208            // records have been loaded, after the USIM service table has been loaded.
209            if (ust != null && ust.isAvailable(
210                    UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
211                Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
212                return mDataDownloadHandler.startDataDownload(sms);
213            } else {
214                Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
215                String smsc = IccUtils.bytesToHexString(
216                        PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
217                                sms.getServiceCenterAddress()));
218                mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
219                        IccUtils.bytesToHexString(sms.getPdu()),
220                        obtainMessage(EVENT_WRITE_SMS_COMPLETE));
221                return Activity.RESULT_OK;  // acknowledge after response from write to USIM
222            }
223        }
224
225        if (mSmsReceiveDisabled) {
226            // Device doesn't support SMS service,
227            Rlog.d(TAG, "Received short message on device which doesn't support "
228                    + "SMS service. Ignored.");
229            return Intents.RESULT_SMS_HANDLED;
230        }
231
232        // Special case the message waiting indicator messages
233        boolean handled = false;
234        if (sms.isMWISetMessage()) {
235            mPhone.setVoiceMessageWaiting(1, -1);  // line 1: unknown number of msgs waiting
236            handled = sms.isMwiDontStore();
237            if (VDBG) {
238                Rlog.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled);
239            }
240        } else if (sms.isMWIClearMessage()) {
241            mPhone.setVoiceMessageWaiting(1, 0);   // line 1: no msgs waiting
242            handled = sms.isMwiDontStore();
243            if (VDBG) {
244                Rlog.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled);
245            }
246        }
247
248        if (handled) {
249            return Intents.RESULT_SMS_HANDLED;
250        }
251
252        if (!mStorageMonitor.isStorageAvailable() &&
253                sms.getMessageClass() != SmsConstants.MessageClass.CLASS_0) {
254            // It's a storable message and there's no storage available.  Bail.
255            // (See TS 23.038 for a description of class 0 messages.)
256            return Intents.RESULT_SMS_OUT_OF_MEMORY;
257        }
258
259        return dispatchNormalMessage(smsb);
260    }
261
262    /** {@inheritDoc} */
263    @Override
264    protected void sendData(String destAddr, String scAddr, int destPort,
265            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
266        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
267                scAddr, destAddr, destPort, data, (deliveryIntent != null));
268        if (pdu != null) {
269            HashMap map =  SmsTrackerMapFactory(destAddr, scAddr, destPort, data, pdu);
270            SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent,
271                    getFormat());
272            sendRawPdu(tracker);
273        } else {
274            Rlog.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
275        }
276    }
277
278    /** {@inheritDoc} */
279    @Override
280    protected void sendText(String destAddr, String scAddr, String text,
281            PendingIntent sentIntent, PendingIntent deliveryIntent) {
282        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
283                scAddr, destAddr, text, (deliveryIntent != null));
284        if (pdu != null) {
285            HashMap map =  SmsTrackerMapFactory(destAddr, scAddr, text, pdu);
286            SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent,
287                    getFormat());
288            sendRawPdu(tracker);
289        } else {
290            Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
291        }
292    }
293
294    /** {@inheritDoc} */
295    @Override
296    protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
297            boolean use7bitOnly) {
298        return SmsMessage.calculateLength(messageBody, use7bitOnly);
299    }
300
301    /** {@inheritDoc} */
302    @Override
303    protected void sendNewSubmitPdu(String destinationAddress, String scAddress,
304            String message, SmsHeader smsHeader, int encoding,
305            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart) {
306        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
307                message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
308                encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
309        if (pdu != null) {
310            HashMap map =  SmsTrackerMapFactory(destinationAddress, scAddress,
311                    message, pdu);
312            SmsTracker tracker = SmsTrackerFactory(map, sentIntent,
313                    deliveryIntent, getFormat());
314            sendRawPdu(tracker);
315        } else {
316            Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
317        }
318    }
319
320    /** {@inheritDoc} */
321    @Override
322    protected void sendSms(SmsTracker tracker) {
323        HashMap<String, Object> map = tracker.mData;
324
325        byte smsc[] = (byte[]) map.get("smsc");
326        byte pdu[] = (byte[]) map.get("pdu");
327
328        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
329
330        if (tracker.mRetryCount > 0) {
331            Rlog.d(TAG, "sendSms: "
332                    + " mRetryCount=" + tracker.mRetryCount
333                    + " mMessageRef=" + tracker.mMessageRef
334                    + " SS=" + mPhone.getServiceState().getState());
335
336            // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
337            //   TP-RD (bit 2) is 1 for retry
338            //   and TP-MR is set to previously failed sms TP-MR
339            if (((0x01 & pdu[0]) == 0x01)) {
340                pdu[0] |= 0x04; // TP-RD
341                pdu[1] = (byte) tracker.mMessageRef; // TP-MR
342            }
343        }
344        Rlog.d(TAG, "sendSms: "
345                +" isIms()="+isIms()
346                +" mRetryCount="+tracker.mRetryCount
347                +" mImsRetry="+tracker.mImsRetry
348                +" mMessageRef="+tracker.mMessageRef
349                +" SS=" +mPhone.getServiceState().getState());
350
351        // sms over gsm is used:
352        //   if sms over IMS is not supported AND
353        //   this is not a retry case after sms over IMS failed
354        //     indicated by mImsRetry > 0
355        if (0 == tracker.mImsRetry && !isIms()) {
356            if (tracker.mRetryCount > 0) {
357                // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
358                //   TP-RD (bit 2) is 1 for retry
359                //   and TP-MR is set to previously failed sms TP-MR
360                if (((0x01 & pdu[0]) == 0x01)) {
361                    pdu[0] |= 0x04; // TP-RD
362                    pdu[1] = (byte) tracker.mMessageRef; // TP-MR
363                }
364            }
365            mCi.sendSMS(IccUtils.bytesToHexString(smsc),
366                    IccUtils.bytesToHexString(pdu), reply);
367        } else {
368            mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
369                    IccUtils.bytesToHexString(pdu), tracker.mImsRetry,
370                    tracker.mMessageRef, reply);
371            // increment it here, so in case of SMS_FAIL_RETRY over IMS
372            // next retry will be sent using IMS request again.
373            tracker.mImsRetry++;
374        }
375    }
376
377    @Override
378    public void sendRetrySms(SmsTracker tracker) {
379        //re-routing to ImsSMSDispatcher
380        mImsSMSDispatcher.sendRetrySms(tracker);
381    }
382    /** {@inheritDoc} */
383    @Override
384    protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
385        mCi.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
386    }
387
388    private static int resultToCause(int rc) {
389        switch (rc) {
390            case Activity.RESULT_OK:
391            case Intents.RESULT_SMS_HANDLED:
392                // Cause code is ignored on success.
393                return 0;
394            case Intents.RESULT_SMS_OUT_OF_MEMORY:
395                return CommandsInterface.GSM_SMS_FAIL_CAUSE_MEMORY_CAPACITY_EXCEEDED;
396            case Intents.RESULT_SMS_GENERIC_ERROR:
397            default:
398                return CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR;
399        }
400    }
401
402    private void onUpdateIccAvailability() {
403        if (mUiccController == null ) {
404            return;
405        }
406
407        UiccCardApplication newUiccApplication =
408                mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP);
409
410        UiccCardApplication app = mUiccApplication.get();
411        if (app != newUiccApplication) {
412            if (app != null) {
413                Rlog.d(TAG, "Removing stale icc objects.");
414                if (mIccRecords.get() != null) {
415                    mIccRecords.get().unregisterForNewSms(this);
416                }
417                mIccRecords.set(null);
418                mUiccApplication.set(null);
419            }
420            if (newUiccApplication != null) {
421                Rlog.d(TAG, "New Uicc application found");
422                mUiccApplication.set(newUiccApplication);
423                mIccRecords.set(newUiccApplication.getIccRecords());
424                if (mIccRecords.get() != null) {
425                    mIccRecords.get().registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
426                }
427            }
428        }
429    }
430
431    /**
432     * Holds all info about a message page needed to assemble a complete
433     * concatenated message
434     */
435    private static final class SmsCbConcatInfo {
436
437        private final SmsCbHeader mHeader;
438        private final SmsCbLocation mLocation;
439
440        public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
441            mHeader = header;
442            mLocation = location;
443        }
444
445        @Override
446        public int hashCode() {
447            return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
448        }
449
450        @Override
451        public boolean equals(Object obj) {
452            if (obj instanceof SmsCbConcatInfo) {
453                SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
454
455                // Two pages match if they have the same serial number (which includes the
456                // geographical scope and update number), and both pages belong to the same
457                // location (PLMN, plus LAC and CID if these are part of the geographical scope).
458                return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
459                        && mLocation.equals(other.mLocation);
460            }
461
462            return false;
463        }
464
465        /**
466         * Compare the location code for this message to the current location code. The match is
467         * relative to the geographical scope of the message, which determines whether the LAC
468         * and Cell ID are saved in mLocation or set to -1 to match all values.
469         *
470         * @param plmn the current PLMN
471         * @param lac the current Location Area (GSM) or Service Area (UMTS)
472         * @param cid the current Cell ID
473         * @return true if this message is valid for the current location; false otherwise
474         */
475        public boolean matchesLocation(String plmn, int lac, int cid) {
476            return mLocation.isInLocationArea(plmn, lac, cid);
477        }
478    }
479
480    // This map holds incomplete concatenated messages waiting for assembly
481    private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
482            new HashMap<SmsCbConcatInfo, byte[][]>();
483
484    /**
485     * Handle 3GPP format SMS-CB message.
486     * @param ar the AsyncResult containing the received PDUs
487     */
488    private void handleBroadcastSms(AsyncResult ar) {
489        try {
490            byte[] receivedPdu = (byte[])ar.result;
491
492            if (VDBG) {
493                for (int i = 0; i < receivedPdu.length; i += 8) {
494                    StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
495                    for (int j = i; j < i + 8 && j < receivedPdu.length; j++) {
496                        int b = receivedPdu[j] & 0xff;
497                        if (b < 0x10) {
498                            sb.append('0');
499                        }
500                        sb.append(Integer.toHexString(b)).append(' ');
501                    }
502                    Rlog.d(TAG, sb.toString());
503                }
504            }
505
506            SmsCbHeader header = new SmsCbHeader(receivedPdu);
507            String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
508            int lac = -1;
509            int cid = -1;
510            android.telephony.CellLocation cl = mPhone.getCellLocation();
511            // Check if cell location is GsmCellLocation.  This is required to support
512            // dual-mode devices such as CDMA/LTE devices that require support for
513            // both 3GPP and 3GPP2 format messages
514            if (cl instanceof GsmCellLocation) {
515                GsmCellLocation cellLocation = (GsmCellLocation)cl;
516                lac = cellLocation.getLac();
517                cid = cellLocation.getCid();
518            }
519
520            SmsCbLocation location;
521            switch (header.getGeographicalScope()) {
522                case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
523                    location = new SmsCbLocation(plmn, lac, -1);
524                    break;
525
526                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
527                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
528                    location = new SmsCbLocation(plmn, lac, cid);
529                    break;
530
531                case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
532                default:
533                    location = new SmsCbLocation(plmn);
534                    break;
535            }
536
537            byte[][] pdus;
538            int pageCount = header.getNumberOfPages();
539            if (pageCount > 1) {
540                // Multi-page message
541                SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
542
543                // Try to find other pages of the same message
544                pdus = mSmsCbPageMap.get(concatInfo);
545
546                if (pdus == null) {
547                    // This is the first page of this message, make room for all
548                    // pages and keep until complete
549                    pdus = new byte[pageCount][];
550
551                    mSmsCbPageMap.put(concatInfo, pdus);
552                }
553
554                // Page parameter is one-based
555                pdus[header.getPageIndex() - 1] = receivedPdu;
556
557                for (int i = 0; i < pdus.length; i++) {
558                    if (pdus[i] == null) {
559                        // Still missing pages, exit
560                        return;
561                    }
562                }
563
564                // Message complete, remove and dispatch
565                mSmsCbPageMap.remove(concatInfo);
566            } else {
567                // Single page message
568                pdus = new byte[1][];
569                pdus[0] = receivedPdu;
570            }
571
572            SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
573            dispatchBroadcastMessage(message);
574
575            // Remove messages that are out of scope to prevent the map from
576            // growing indefinitely, containing incomplete messages that were
577            // never assembled
578            Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
579
580            while (iter.hasNext()) {
581                SmsCbConcatInfo info = iter.next();
582
583                if (!info.matchesLocation(plmn, lac, cid)) {
584                    iter.remove();
585                }
586            }
587        } catch (RuntimeException e) {
588            Rlog.e(TAG, "Error in decoding SMS CB pdu", e);
589        }
590    }
591
592    @Override
593    public boolean isIms() {
594        return mImsSMSDispatcher.isIms();
595    }
596
597    @Override
598    public String getImsSmsFormat() {
599        return mImsSMSDispatcher.getImsSmsFormat();
600    }
601}
602