/* * Copyright (C) 2008 The Android Open Source Project * Copyright (c) 2013, The Linux Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.Manifest; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.os.AsyncResult; import android.os.Binder; import android.os.Handler; import android.os.Message; import android.os.ServiceManager; import android.telephony.Rlog; import android.util.Log; import com.android.internal.telephony.ISms; import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; import com.android.internal.telephony.uicc.IccConstants; import com.android.internal.telephony.uicc.IccFileHandler; import com.android.internal.util.HexDump; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static android.telephony.SmsManager.STATUS_ON_ICC_FREE; import static android.telephony.SmsManager.STATUS_ON_ICC_READ; import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD; /** * IccSmsInterfaceManager to provide an inter-process communication to * access Sms in Icc. */ public class IccSmsInterfaceManager extends ISms.Stub { static final String LOG_TAG = "IccSmsInterfaceManager"; static final boolean DBG = true; protected final Object mLock = new Object(); protected boolean mSuccess; private List mSms; private CellBroadcastRangeManager mCellBroadcastRangeManager = new CellBroadcastRangeManager(); private CdmaBroadcastRangeManager mCdmaBroadcastRangeManager = new CdmaBroadcastRangeManager(); private static final int EVENT_LOAD_DONE = 1; private static final int EVENT_UPDATE_DONE = 2; protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3; protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4; private static final int SMS_CB_CODE_SCHEME_MIN = 0; private static final int SMS_CB_CODE_SCHEME_MAX = 255; protected PhoneBase mPhone; final protected Context mContext; final protected AppOpsManager mAppOps; protected SMSDispatcher mDispatcher; protected Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { AsyncResult ar; switch (msg.what) { case EVENT_UPDATE_DONE: ar = (AsyncResult) msg.obj; synchronized (mLock) { mSuccess = (ar.exception == null); mLock.notifyAll(); } break; case EVENT_LOAD_DONE: ar = (AsyncResult)msg.obj; synchronized (mLock) { if (ar.exception == null) { mSms = (List) buildValidRawData((ArrayList) ar.result); //Mark SMS as read after importing it from card. markMessagesAsRead((ArrayList) ar.result); } else { if (Rlog.isLoggable("SMS", Log.DEBUG)) { log("Cannot load Sms records"); } if (mSms != null) mSms.clear(); } mLock.notifyAll(); } break; case EVENT_SET_BROADCAST_ACTIVATION_DONE: case EVENT_SET_BROADCAST_CONFIG_DONE: ar = (AsyncResult) msg.obj; synchronized (mLock) { mSuccess = (ar.exception == null); mLock.notifyAll(); } break; } } }; protected IccSmsInterfaceManager(PhoneBase phone) { mPhone = phone; mContext = phone.getContext(); mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mDispatcher = new ImsSMSDispatcher(phone, phone.mSmsStorageMonitor, phone.mSmsUsageMonitor); if (ServiceManager.getService("isms") == null) { ServiceManager.addService("isms", this); } } protected void markMessagesAsRead(ArrayList messages) { if (messages == null) { return; } //IccFileHandler can be null, if icc card is absent. IccFileHandler fh = mPhone.getIccFileHandler(); if (fh == null) { //shouldn't really happen, as messages are marked as read, only //after importing it from icc. if (Rlog.isLoggable("SMS", Log.DEBUG)) { log("markMessagesAsRead - aborting, no icc card present."); } return; } int count = messages.size(); for (int i = 0; i < count; i++) { byte[] ba = messages.get(i); if (ba[0] == STATUS_ON_ICC_UNREAD) { int n = ba.length; byte[] nba = new byte[n - 1]; System.arraycopy(ba, 1, nba, 0, n - 1); byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba); fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null); if (Rlog.isLoggable("SMS", Log.DEBUG)) { log("SMS " + (i + 1) + " marked as read"); } } } } public void dispose() { mDispatcher.dispose(); } protected void updatePhoneObject(PhoneBase phone) { mPhone = phone; mDispatcher.updatePhoneObject(phone); } protected void enforceReceiveAndSend(String message) { mContext.enforceCallingPermission( Manifest.permission.RECEIVE_SMS, message); mContext.enforceCallingPermission( Manifest.permission.SEND_SMS, message); } /** * Update the specified message on the Icc. * * @param index record index of message to update * @param status new message status (STATUS_ON_ICC_READ, * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT, * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE) * @param pdu the raw PDU to store * @return success or not * */ @Override public boolean updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) { if (DBG) log("updateMessageOnIccEf: index=" + index + " status=" + status + " ==> " + "("+ Arrays.toString(pdu) + ")"); enforceReceiveAndSend("Updating message on Icc"); if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return false; } synchronized(mLock) { mSuccess = false; Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE); if (status == STATUS_ON_ICC_FREE) { // RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM // Special case FREE: call deleteSmsOnSim/Ruim instead of // manipulating the record // Will eventually fail if icc card is not present. if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) { mPhone.mCi.deleteSmsOnSim(index, response); } else { mPhone.mCi.deleteSmsOnRuim(index, response); } } else { //IccFilehandler can be null if ICC card is not present. IccFileHandler fh = mPhone.getIccFileHandler(); if (fh == null) { response.recycle(); return mSuccess; /* is false */ } byte[] record = makeSmsRecordData(status, pdu); fh.updateEFLinearFixed( IccConstants.EF_SMS, index, record, null, response); } try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to update by index"); } } return mSuccess; } /** * Copy a raw SMS PDU to the Icc. * * @param pdu the raw PDU to store * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD, * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT) * @return success or not * */ @Override public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) { //NOTE smsc not used in RUIM if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " + "pdu=("+ Arrays.toString(pdu) + "), smsc=(" + Arrays.toString(smsc) +")"); enforceReceiveAndSend("Copying message to Icc"); if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return false; } synchronized(mLock) { mSuccess = false; Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE); //RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) { mPhone.mCi.writeSmsToSim(status, IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), response); } else { mPhone.mCi.writeSmsToRuim(status, IccUtils.bytesToHexString(pdu), response); } try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to update by index"); } } return mSuccess; } /** * Retrieves all messages currently stored on Icc. * * @return list of SmsRawData of all sms on Icc */ @Override public List getAllMessagesFromIccEf(String callingPackage) { if (DBG) log("getAllMessagesFromEF"); mContext.enforceCallingOrSelfPermission( Manifest.permission.RECEIVE_SMS, "Reading messages from Icc"); if (mAppOps.noteOp(AppOpsManager.OP_READ_ICC_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return new ArrayList(); } synchronized(mLock) { IccFileHandler fh = mPhone.getIccFileHandler(); if (fh == null) { Rlog.e(LOG_TAG, "Cannot load Sms records. No icc card?"); if (mSms != null) { mSms.clear(); return mSms; } } Message response = mHandler.obtainMessage(EVENT_LOAD_DONE); fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response); try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to load from the Icc"); } } return mSms; } /** * Send a data based SMS to a specific application port. * * @param destAddr the address to send the message to * @param scAddr is the service center address or null to use * the current default SMSC * @param destPort the port to deliver the message to * @param data the body of the message to send * @param sentIntent if not NULL this PendingIntent is * broadcast when the message is successfully sent, or failed. * The result code will be Activity.RESULT_OK for success, * or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.
* The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this PendingIntent is * broadcast when the message is delivered to the recipient. The * raw pdu of the status report is in the extended data ("pdu"). */ @Override public void sendData(String callingPackage, String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { mPhone.getContext().enforceCallingPermission( Manifest.permission.SEND_SMS, "Sending SMS message"); if (Rlog.isLoggable("SMS", Log.VERBOSE)) { log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" + destPort + " data='"+ HexDump.toHexString(data) + "' sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent); } if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } mDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent); } /** * Send a text based SMS. * * @param destAddr the address to send the message to * @param scAddr is the service center address or null to use * the current default SMSC * @param text the body of the message to send * @param sentIntent if not NULL this PendingIntent is * broadcast when the message is successfully sent, or failed. * The result code will be Activity.RESULT_OK for success, * or one of these errors:
* RESULT_ERROR_GENERIC_FAILURE
* RESULT_ERROR_RADIO_OFF
* RESULT_ERROR_NULL_PDU
* For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.
* The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this PendingIntent is * broadcast when the message is delivered to the recipient. The * raw pdu of the status report is in the extended data ("pdu"). */ @Override public void sendText(String callingPackage, String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) { mPhone.getContext().enforceCallingPermission( Manifest.permission.SEND_SMS, "Sending SMS message"); if (Rlog.isLoggable("SMS", Log.VERBOSE)) { log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr + " text='"+ text + "' sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent); } if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent); } /** * Send a multi-part text based SMS. * * @param destAddr the address to send the message to * @param scAddr is the service center address or null to use * the current default SMSC * @param parts an ArrayList of strings that, in order, * comprise the original message * @param sentIntents if not null, an ArrayList of * PendingIntents (one for each message part) that is * broadcast when the corresponding message part has been sent. * The result code will be Activity.RESULT_OK for success, * or one of these errors: * RESULT_ERROR_GENERIC_FAILURE * RESULT_ERROR_RADIO_OFF * RESULT_ERROR_NULL_PDU. * The per-application based SMS control checks sentIntent. If sentIntent * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntents if not null, an ArrayList of * PendingIntents (one for each message part) that is * broadcast when the corresponding message part has been delivered * to the recipient. The raw pdu of the status report is in the * extended data ("pdu"). */ @Override public void sendMultipartText(String callingPackage, String destAddr, String scAddr, List parts, List sentIntents, List deliveryIntents) { mPhone.getContext().enforceCallingPermission( Manifest.permission.SEND_SMS, "Sending SMS message"); if (Rlog.isLoggable("SMS", Log.VERBOSE)) { int i = 0; for (String part : parts) { log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr + ", part[" + (i++) + "]=" + part); } } if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList) parts, (ArrayList) sentIntents, (ArrayList) deliveryIntents); } @Override public int getPremiumSmsPermission(String packageName) { return mDispatcher.getPremiumSmsPermission(packageName); } @Override public void setPremiumSmsPermission(String packageName, int permission) { mDispatcher.setPremiumSmsPermission(packageName, permission); } /** * create SmsRawData lists from all sms record byte[] * Use null to indicate "free" record * * @param messages List of message records from EF_SMS. * @return SmsRawData list of all in-used records */ protected ArrayList buildValidRawData(ArrayList messages) { int count = messages.size(); ArrayList ret; ret = new ArrayList(count); for (int i = 0; i < count; i++) { byte[] ba = messages.get(i); if (ba[0] == STATUS_ON_ICC_FREE) { ret.add(null); } else { ret.add(new SmsRawData(messages.get(i))); } } return ret; } /** * Generates an EF_SMS record from status and raw PDU. * * @param status Message status. See TS 51.011 10.5.3. * @param pdu Raw message PDU. * @return byte array for the record. */ protected byte[] makeSmsRecordData(int status, byte[] pdu) { byte[] data = new byte[IccConstants.SMS_RECORD_LENGTH]; // Status bits for this record. See TS 51.011 10.5.3 data[0] = (byte)(status & 7); System.arraycopy(pdu, 0, data, 1, pdu.length); // Pad out with 0xFF's. for (int j = pdu.length+1; j < IccConstants.SMS_RECORD_LENGTH; j++) { data[j] = -1; } return data; } public boolean enableCellBroadcast(int messageIdentifier) { return enableCellBroadcastRange(messageIdentifier, messageIdentifier); } public boolean disableCellBroadcast(int messageIdentifier) { return disableCellBroadcastRange(messageIdentifier, messageIdentifier); } public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) { if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) { return enableGsmBroadcastRange(startMessageId, endMessageId); } else { return enableCdmaBroadcastRange(startMessageId, endMessageId); } } public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) { if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) { return disableGsmBroadcastRange(startMessageId, endMessageId); } else { return disableCdmaBroadcastRange(startMessageId, endMessageId); } } synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) { if (DBG) log("enableGsmBroadcastRange"); Context context = mPhone.getContext(); context.enforceCallingPermission( "android.permission.RECEIVE_SMS", "Enabling cell broadcast SMS"); String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) { log("Failed to add cell broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); return false; } if (DBG) log("Added cell broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty()); return true; } synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) { if (DBG) log("disableGsmBroadcastRange"); Context context = mPhone.getContext(); context.enforceCallingPermission( "android.permission.RECEIVE_SMS", "Disabling cell broadcast SMS"); String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) { log("Failed to remove cell broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); return false; } if (DBG) log("Removed cell broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty()); return true; } synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) { if (DBG) log("enableCdmaBroadcastRange"); Context context = mPhone.getContext(); context.enforceCallingPermission( "android.permission.RECEIVE_SMS", "Enabling cdma broadcast SMS"); String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); if (!mCdmaBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) { log("Failed to add cdma broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); return false; } if (DBG) log("Added cdma broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty()); return true; } synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) { if (DBG) log("disableCdmaBroadcastRange"); Context context = mPhone.getContext(); context.enforceCallingPermission( "android.permission.RECEIVE_SMS", "Disabling cell broadcast SMS"); String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); if (!mCdmaBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) { log("Failed to remove cdma broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); return false; } if (DBG) log("Removed cdma broadcast subscription for MID range " + startMessageId + " to " + endMessageId + " from client " + client); setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty()); return true; } class CellBroadcastRangeManager extends IntRangeManager { private ArrayList mConfigList = new ArrayList(); /** * Called when the list of enabled ranges has changed. This will be * followed by zero or more calls to {@link #addRange} followed by * a call to {@link #finishUpdate}. */ protected void startUpdate() { mConfigList.clear(); } /** * Called after {@link #startUpdate} to indicate a range of enabled * values. * @param startId the first id included in the range * @param endId the last id included in the range */ protected void addRange(int startId, int endId, boolean selected) { mConfigList.add(new SmsBroadcastConfigInfo(startId, endId, SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected)); } /** * Called to indicate the end of a range update started by the * previous call to {@link #startUpdate}. * @return true if successful, false otherwise */ protected boolean finishUpdate() { if (mConfigList.isEmpty()) { return true; } else { SmsBroadcastConfigInfo[] configs = mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]); return setCellBroadcastConfig(configs); } } } class CdmaBroadcastRangeManager extends IntRangeManager { private ArrayList mConfigList = new ArrayList(); /** * Called when the list of enabled ranges has changed. This will be * followed by zero or more calls to {@link #addRange} followed by a * call to {@link #finishUpdate}. */ protected void startUpdate() { mConfigList.clear(); } /** * Called after {@link #startUpdate} to indicate a range of enabled * values. * @param startId the first id included in the range * @param endId the last id included in the range */ protected void addRange(int startId, int endId, boolean selected) { mConfigList.add(new CdmaSmsBroadcastConfigInfo(startId, endId, 1, selected)); } /** * Called to indicate the end of a range update started by the previous * call to {@link #startUpdate}. * @return true if successful, false otherwise */ protected boolean finishUpdate() { if (mConfigList.isEmpty()) { return true; } else { CdmaSmsBroadcastConfigInfo[] configs = mConfigList.toArray(new CdmaSmsBroadcastConfigInfo[mConfigList.size()]); return setCdmaBroadcastConfig(configs); } } } private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) { if (DBG) log("Calling setGsmBroadcastConfig with " + configs.length + " configurations"); synchronized (mLock) { Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE); mSuccess = false; mPhone.mCi.setGsmBroadcastConfig(configs, response); try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to set cell broadcast config"); } } return mSuccess; } private boolean setCellBroadcastActivation(boolean activate) { if (DBG) log("Calling setCellBroadcastActivation(" + activate + ')'); synchronized (mLock) { Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE); mSuccess = false; mPhone.mCi.setGsmBroadcastActivation(activate, response); try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to set cell broadcast activation"); } } return mSuccess; } private boolean setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs) { if (DBG) log("Calling setCdmaBroadcastConfig with " + configs.length + " configurations"); synchronized (mLock) { Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE); mSuccess = false; mPhone.mCi.setCdmaBroadcastConfig(configs, response); try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to set cdma broadcast config"); } } return mSuccess; } private boolean setCdmaBroadcastActivation(boolean activate) { if (DBG) log("Calling setCdmaBroadcastActivation(" + activate + ")"); synchronized (mLock) { Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE); mSuccess = false; mPhone.mCi.setCdmaBroadcastActivation(activate, response); try { mLock.wait(); } catch (InterruptedException e) { log("interrupted while trying to set cdma broadcast activation"); } } return mSuccess; } protected void log(String msg) { Log.d(LOG_TAG, "[IccSmsInterfaceManager] " + msg); } public boolean isImsSmsSupported() { return mDispatcher.isIms(); } public String getImsSmsFormat() { return mDispatcher.getImsSmsFormat(); } }