/* * Copyright (C) 2007 The Android Open Source Project * * 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.cat; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources.NotFoundException; import android.os.AsyncResult; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.SystemProperties; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.uicc.IccFileHandler; import com.android.internal.telephony.uicc.IccRecords; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.UiccCard; import com.android.internal.telephony.uicc.UiccCardApplication; import com.android.internal.telephony.uicc.IccCardStatus.CardState; import com.android.internal.telephony.uicc.IccRefreshResponse; import com.android.internal.telephony.uicc.UiccController; import java.io.ByteArrayOutputStream; import java.util.List; import java.util.Locale; import static com.android.internal.telephony.cat.CatCmdMessage. SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT; import static com.android.internal.telephony.cat.CatCmdMessage. SetupEventListConstants.LANGUAGE_SELECTION_EVENT; class RilMessage { int mId; Object mData; ResultCode mResCode; RilMessage(int msgId, String rawData) { mId = msgId; mData = rawData; } RilMessage(RilMessage other) { mId = other.mId; mData = other.mData; mResCode = other.mResCode; } } /** * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL * and application. * * {@hide} */ public class CatService extends Handler implements AppInterface { private static final boolean DBG = false; // Class members private static IccRecords mIccRecords; private static UiccCardApplication mUiccApplication; // Service members. // Protects singleton instance lazy initialization. private static final Object sInstanceLock = new Object(); private static CatService[] sInstance = null; private CommandsInterface mCmdIf; private Context mContext; private CatCmdMessage mCurrntCmd = null; private CatCmdMessage mMenuCmd = null; private RilMessageDecoder mMsgDecoder = null; private boolean mStkAppInstalled = false; private UiccController mUiccController; private CardState mCardState = CardState.CARDSTATE_ABSENT; // Service constants. protected static final int MSG_ID_SESSION_END = 1; protected static final int MSG_ID_PROACTIVE_COMMAND = 2; protected static final int MSG_ID_EVENT_NOTIFY = 3; protected static final int MSG_ID_CALL_SETUP = 4; static final int MSG_ID_REFRESH = 5; static final int MSG_ID_RESPONSE = 6; static final int MSG_ID_SIM_READY = 7; protected static final int MSG_ID_ICC_CHANGED = 8; protected static final int MSG_ID_ALPHA_NOTIFY = 9; static final int MSG_ID_RIL_MSG_DECODED = 10; // Events to signal SIM presence or absent in the device. private static final int MSG_ID_ICC_RECORDS_LOADED = 20; //Events to signal SIM REFRESH notificatations private static final int MSG_ID_ICC_REFRESH = 30; private static final int DEV_ID_KEYPAD = 0x01; private static final int DEV_ID_DISPLAY = 0x02; private static final int DEV_ID_UICC = 0x81; private static final int DEV_ID_TERMINAL = 0x82; private static final int DEV_ID_NETWORK = 0x83; static final String STK_DEFAULT = "Default Message"; private HandlerThread mHandlerThread; private int mSlotId; /* For multisim catservice should not be singleton */ private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, Context context, IccFileHandler fh, UiccCard ic, int slotId) { if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) { throw new NullPointerException( "Service: Input parameters must not be null"); } mCmdIf = ci; mContext = context; mSlotId = slotId; mHandlerThread = new HandlerThread("Cat Telephony service" + slotId); mHandlerThread.start(); // Get the RilMessagesDecoder for decoding the messages. mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId); if (null == mMsgDecoder) { CatLog.d(this, "Null RilMessageDecoder instance"); return; } mMsgDecoder.start(); // Register ril events handling. mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null); mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null); mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null); mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null); //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null); mCmdIf.registerForIccRefresh(this, MSG_ID_ICC_REFRESH, null); mCmdIf.setOnCatCcAlphaNotify(this, MSG_ID_ALPHA_NOTIFY, null); mIccRecords = ir; mUiccApplication = ca; // Register for SIM ready event. mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null); CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this); mUiccController = UiccController.getInstance(); mUiccController.registerForIccChanged(this, MSG_ID_ICC_CHANGED, null); // Check if STK application is available mStkAppInstalled = isStkAppInstalled(); CatLog.d(this, "Running CAT service on Slotid: " + mSlotId + ". STK app installed:" + mStkAppInstalled); } /** * Used for instantiating the Service from the Card. * * @param ci CommandsInterface object * @param context phone app context * @param ic Icc card * @param slotId to know the index of card * @return The only Service object in the system */ public static CatService getInstance(CommandsInterface ci, Context context, UiccCard ic, int slotId) { UiccCardApplication ca = null; IccFileHandler fh = null; IccRecords ir = null; if (ic != null) { /* Since Cat is not tied to any application, but rather is Uicc application * in itself - just get first FileHandler and IccRecords object */ ca = ic.getApplicationIndex(0); if (ca != null) { fh = ca.getIccFileHandler(); ir = ca.getIccRecords(); } } synchronized (sInstanceLock) { if (sInstance == null) { int simCount = TelephonyManager.getDefault().getSimCount(); sInstance = new CatService[simCount]; for (int i = 0; i < simCount; i++) { sInstance[i] = null; } } if (sInstance[slotId] == null) { if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) { return null; } sInstance[slotId] = new CatService(ci, ca, ir, context, fh, ic, slotId); } else if ((ir != null) && (mIccRecords != ir)) { if (mIccRecords != null) { mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]); } mIccRecords = ir; mUiccApplication = ca; mIccRecords.registerForRecordsLoaded(sInstance[slotId], MSG_ID_ICC_RECORDS_LOADED, null); CatLog.d(sInstance[slotId], "registerForRecordsLoaded slotid=" + slotId + " instance:" + sInstance[slotId]); } return sInstance[slotId]; } } public void dispose() { synchronized (sInstanceLock) { CatLog.d(this, "Disposing CatService object"); mIccRecords.unregisterForRecordsLoaded(this); // Clean up stk icon if dispose is called broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_ABSENT, null); mCmdIf.unSetOnCatSessionEnd(this); mCmdIf.unSetOnCatProactiveCmd(this); mCmdIf.unSetOnCatEvent(this); mCmdIf.unSetOnCatCallSetUp(this); mCmdIf.unSetOnCatCcAlphaNotify(this); mCmdIf.unregisterForIccRefresh(this); if (mUiccController != null) { mUiccController.unregisterForIccChanged(this); mUiccController = null; } mMsgDecoder.dispose(); mMsgDecoder = null; mHandlerThread.quit(); mHandlerThread = null; removeCallbacksAndMessages(null); if (sInstance != null) { if (SubscriptionManager.isValidSlotId(mSlotId)) { sInstance[mSlotId] = null; } else { CatLog.d(this, "error: invaild slot id: " + mSlotId); } } } } @Override protected void finalize() { CatLog.d(this, "Service finalized"); } private void handleRilMsg(RilMessage rilMsg) { if (rilMsg == null) { return; } // dispatch messages CommandParams cmdParams = null; switch (rilMsg.mId) { case MSG_ID_EVENT_NOTIFY: if (rilMsg.mResCode == ResultCode.OK) { cmdParams = (CommandParams) rilMsg.mData; if (cmdParams != null) { handleCommand(cmdParams, false); } } break; case MSG_ID_PROACTIVE_COMMAND: try { cmdParams = (CommandParams) rilMsg.mData; } catch (ClassCastException e) { // for error handling : cast exception CatLog.d(this, "Fail to parse proactive command"); // Don't send Terminal Resp if command detail is not available if (mCurrntCmd != null) { sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD, false, 0x00, null); } break; } if (cmdParams != null) { if (rilMsg.mResCode == ResultCode.OK) { handleCommand(cmdParams, true); } else { // for proactive commands that couldn't be decoded // successfully respond with the code generated by the // message decoder. sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode, false, 0, null); } } break; case MSG_ID_REFRESH: cmdParams = (CommandParams) rilMsg.mData; if (cmdParams != null) { handleCommand(cmdParams, false); } break; case MSG_ID_SESSION_END: handleSessionEnd(); break; case MSG_ID_CALL_SETUP: // prior event notify command supplied all the information // needed for set up call processing. break; } } /** * This function validates the events in SETUP_EVENT_LIST which are currently * supported by the Android framework. In case of SETUP_EVENT_LIST has NULL events * or no events, all the events need to be reset. */ private boolean isSupportedSetupEventCommand(CatCmdMessage cmdMsg) { boolean flag = true; for (int eventVal: cmdMsg.getSetEventList().eventList) { CatLog.d(this,"Event: " + eventVal); switch (eventVal) { /* Currently android is supporting only the below events in SetupEventList * Language Selection. */ case IDLE_SCREEN_AVAILABLE_EVENT: case LANGUAGE_SELECTION_EVENT: break; default: flag = false; } } return flag; } /** * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command * from RIL. * Sends valid proactive command data to the application using intents. * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is * from RIL_UNSOL_STK_PROACTIVE_COMMAND. */ private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) { CatLog.d(this, cmdParams.getCommandType().name()); // Log all proactive commands. if (isProactiveCmd) { if (mUiccController != null) { mUiccController.addCardLog("ProactiveCommand mSlotId=" + mSlotId + " cmdParams=" + cmdParams); } } CharSequence message; ResultCode resultCode; CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams); switch (cmdParams.getCommandType()) { case SET_UP_MENU: if (removeMenu(cmdMsg.getMenu())) { mMenuCmd = null; } else { mMenuCmd = cmdMsg; } resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK; sendTerminalResponse(cmdParams.mCmdDet, resultCode, false, 0, null); break; case DISPLAY_TEXT: break; case REFRESH: // ME side only handles refresh commands which meant to remove IDLE // MODE TEXT. cmdParams.mCmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value(); break; case SET_UP_IDLE_MODE_TEXT: resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK; sendTerminalResponse(cmdParams.mCmdDet,resultCode, false, 0, null); break; case SET_UP_EVENT_LIST: if (isSupportedSetupEventCommand(cmdMsg)) { sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); } else { sendTerminalResponse(cmdParams.mCmdDet, ResultCode.BEYOND_TERMINAL_CAPABILITY, false, 0, null); } break; case PROVIDE_LOCAL_INFORMATION: ResponseData resp; switch (cmdParams.mCmdDet.commandQualifier) { case CommandParamsFactory.DTTZ_SETTING: resp = new DTTZResponseData(null); sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp); break; case CommandParamsFactory.LANGUAGE_SETTING: resp = new LanguageResponseData(Locale.getDefault().getLanguage()); sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp); break; default: sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); } // No need to start STK app here. return; case LAUNCH_BROWSER: if ((((LaunchBrowserParams) cmdParams).mConfirmMsg.text != null) && (((LaunchBrowserParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) { message = mContext.getText(com.android.internal.R.string.launchBrowserDefault); ((LaunchBrowserParams) cmdParams).mConfirmMsg.text = message.toString(); } break; case SELECT_ITEM: case GET_INPUT: case GET_INKEY: break; case SEND_DTMF: case SEND_SMS: case SEND_SS: case SEND_USSD: if ((((DisplayTextParams)cmdParams).mTextMsg.text != null) && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) { message = mContext.getText(com.android.internal.R.string.sending); ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString(); } break; case PLAY_TONE: break; case SET_UP_CALL: if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null) && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) { message = mContext.getText(com.android.internal.R.string.SetupCallDefault); ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString(); } break; case OPEN_CHANNEL: case CLOSE_CHANNEL: case RECEIVE_DATA: case SEND_DATA: BIPClientParams cmd = (BIPClientParams) cmdParams; /* Per 3GPP specification 102.223, * if the alpha identifier is not provided by the UICC, * the terminal MAY give information to the user * noAlphaUsrCnf defines if you need to show user confirmation or not */ boolean noAlphaUsrCnf = false; try { noAlphaUsrCnf = mContext.getResources().getBoolean( com.android.internal.R.bool.config_stkNoAlphaUsrCnf); } catch (NotFoundException e) { noAlphaUsrCnf = false; } if ((cmd.mTextMsg.text == null) && (cmd.mHasAlphaId || noAlphaUsrCnf)) { CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id"); // If alpha length is zero, we just respond with OK. if (isProactiveCmd) { sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); } else if (cmdParams.getCommandType() == CommandType.OPEN_CHANNEL) { mCmdIf.handleCallSetupRequestFromSim(true, null); } return; } // Respond with permanent failure to avoid retry if STK app is not present. if (!mStkAppInstalled) { CatLog.d(this, "No STK application found."); if (isProactiveCmd) { sendTerminalResponse(cmdParams.mCmdDet, ResultCode.BEYOND_TERMINAL_CAPABILITY, false, 0, null); return; } } /* * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by * either PROACTIVE_COMMAND or EVENT_NOTIFY. * If PROACTIVE_COMMAND is used for those commands, send terminal * response here. */ if (isProactiveCmd && ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) || (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) || (cmdParams.getCommandType() == CommandType.SEND_DATA))) { sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null); } break; default: CatLog.d(this, "Unsupported command"); return; } mCurrntCmd = cmdMsg; broadcastCatCmdIntent(cmdMsg); } private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) { Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra("STK CMD", cmdMsg); intent.putExtra("SLOT_ID", mSlotId); CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId); mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); } /** * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL. * */ private void handleSessionEnd() { CatLog.d(this, "SESSION END on "+ mSlotId); mCurrntCmd = mMenuCmd; Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION); intent.putExtra("SLOT_ID", mSlotId); mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); } private void sendTerminalResponse(CommandDetails cmdDet, ResultCode resultCode, boolean includeAdditionalInfo, int additionalInfo, ResponseData resp) { if (cmdDet == null) { return; } ByteArrayOutputStream buf = new ByteArrayOutputStream(); Input cmdInput = null; if (mCurrntCmd != null) { cmdInput = mCurrntCmd.geInput(); } // command details int tag = ComprehensionTlvTag.COMMAND_DETAILS.value(); if (cmdDet.compRequired) { tag |= 0x80; } buf.write(tag); buf.write(0x03); // length buf.write(cmdDet.commandNumber); buf.write(cmdDet.typeOfCommand); buf.write(cmdDet.commandQualifier); // device identities // According to TS102.223/TS31.111 section 6.8 Structure of // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, // the ME should set the CR(comprehension required) flag to // comprehension not required.(CR=0)" // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, // the CR flag is not set. tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value(); buf.write(tag); buf.write(0x02); // length buf.write(DEV_ID_TERMINAL); // source device id buf.write(DEV_ID_UICC); // destination device id // result tag = ComprehensionTlvTag.RESULT.value(); if (cmdDet.compRequired) { tag |= 0x80; } buf.write(tag); int length = includeAdditionalInfo ? 2 : 1; buf.write(length); buf.write(resultCode.value()); // additional info if (includeAdditionalInfo) { buf.write(additionalInfo); } // Fill optional data for each corresponding command if (resp != null) { resp.format(buf); } else { encodeOptionalTags(cmdDet, resultCode, cmdInput, buf); } byte[] rawData = buf.toByteArray(); String hexString = IccUtils.bytesToHexString(rawData); if (DBG) { CatLog.d(this, "TERMINAL RESPONSE: " + hexString); } mCmdIf.sendTerminalResponse(hexString, null); } private void encodeOptionalTags(CommandDetails cmdDet, ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) { CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand); if (cmdType != null) { switch (cmdType) { case GET_INKEY: // ETSI TS 102 384,27.22.4.2.8.4.2. // If it is a response for GET_INKEY command and the response timeout // occured, then add DURATION TLV for variable timeout case. if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) && (cmdInput != null) && (cmdInput.duration != null)) { getInKeyResponse(buf, cmdInput); } break; case PROVIDE_LOCAL_INFORMATION: if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) && (resultCode.value() == ResultCode.OK.value())) { getPliResponse(buf); } break; default: CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet); break; } } else { CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet); } } private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) { int tag = ComprehensionTlvTag.DURATION.value(); buf.write(tag); buf.write(0x02); // length buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds) buf.write(cmdInput.duration.timeInterval); // Time Duration } private void getPliResponse(ByteArrayOutputStream buf) { // Locale Language Setting final String lang = Locale.getDefault().getLanguage(); if (lang != null) { // tag int tag = ComprehensionTlvTag.LANGUAGE.value(); buf.write(tag); ResponseData.writeLength(buf, lang.length()); buf.write(lang.getBytes(), 0, lang.length()); } } private void sendMenuSelection(int menuId, boolean helpRequired) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); // tag int tag = BerTlv.BER_MENU_SELECTION_TAG; buf.write(tag); // length buf.write(0x00); // place holder // device identities tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); buf.write(tag); buf.write(0x02); // length buf.write(DEV_ID_KEYPAD); // source device id buf.write(DEV_ID_UICC); // destination device id // item identifier tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value(); buf.write(tag); buf.write(0x01); // length buf.write(menuId); // menu identifier chosen // help request if (helpRequired) { tag = ComprehensionTlvTag.HELP_REQUEST.value(); buf.write(tag); buf.write(0x00); // length } byte[] rawData = buf.toByteArray(); // write real length int len = rawData.length - 2; // minus (tag + length) rawData[1] = (byte) len; String hexString = IccUtils.bytesToHexString(rawData); mCmdIf.sendEnvelope(hexString, null); } private void eventDownload(int event, int sourceId, int destinationId, byte[] additionalInfo, boolean oneShot) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); // tag int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG; buf.write(tag); // length buf.write(0x00); // place holder, assume length < 128. // event list tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value(); buf.write(tag); buf.write(0x01); // length buf.write(event); // event value // device identities tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value(); buf.write(tag); buf.write(0x02); // length buf.write(sourceId); // source device id buf.write(destinationId); // destination device id /* * Check for type of event download to be sent to UICC - Browser * termination,Idle screen available, User activity, Language selection * etc as mentioned under ETSI TS 102 223 section 7.5 */ /* * Currently the below events are supported: * Language Selection Event. * Other event download commands should be encoded similar way */ /* TODO: eventDownload should be extended for other Envelope Commands */ switch (event) { case IDLE_SCREEN_AVAILABLE_EVENT: CatLog.d(sInstance, " Sending Idle Screen Available event download to ICC"); break; case LANGUAGE_SELECTION_EVENT: CatLog.d(sInstance, " Sending Language Selection event download to ICC"); tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value(); buf.write(tag); // Language length should be 2 byte buf.write(0x02); break; default: break; } // additional information if (additionalInfo != null) { for (byte b : additionalInfo) { buf.write(b); } } byte[] rawData = buf.toByteArray(); // write real length int len = rawData.length - 2; // minus (tag + length) rawData[1] = (byte) len; String hexString = IccUtils.bytesToHexString(rawData); CatLog.d(this, "ENVELOPE COMMAND: " + hexString); mCmdIf.sendEnvelope(hexString, null); } /** * Used by application to get an AppInterface object. * * @return The only Service object in the system */ //TODO Need to take care for MSIM public static AppInterface getInstance() { int slotId = PhoneConstants.DEFAULT_CARD_INDEX; SubscriptionController sControl = SubscriptionController.getInstance(); if (sControl != null) { slotId = sControl.getSlotId(sControl.getDefaultSubId()); } return getInstance(null, null, null, slotId); } /** * Used by application to get an AppInterface object. * * @return The only Service object in the system */ public static AppInterface getInstance(int slotId) { return getInstance(null, null, null, slotId); } @Override public void handleMessage(Message msg) { CatLog.d(this, "handleMessage[" + msg.what + "]"); switch (msg.what) { case MSG_ID_SESSION_END: case MSG_ID_PROACTIVE_COMMAND: case MSG_ID_EVENT_NOTIFY: case MSG_ID_REFRESH: CatLog.d(this, "ril message arrived,slotid:" + mSlotId); String data = null; if (msg.obj != null) { AsyncResult ar = (AsyncResult) msg.obj; if (ar != null && ar.result != null) { try { data = (String) ar.result; } catch (ClassCastException e) { break; } } } mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data)); break; case MSG_ID_CALL_SETUP: mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null)); break; case MSG_ID_ICC_RECORDS_LOADED: break; case MSG_ID_RIL_MSG_DECODED: handleRilMsg((RilMessage) msg.obj); break; case MSG_ID_RESPONSE: handleCmdResponse((CatResponseMessage) msg.obj); break; case MSG_ID_ICC_CHANGED: CatLog.d(this, "MSG_ID_ICC_CHANGED"); updateIccAvailability(); break; case MSG_ID_ICC_REFRESH: if (msg.obj != null) { AsyncResult ar = (AsyncResult) msg.obj; if (ar != null && ar.result != null) { broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_PRESENT, (IccRefreshResponse) ar.result); } else { CatLog.d(this,"Icc REFRESH with exception: " + ar.exception); } } else { CatLog.d(this, "IccRefresh Message is null"); } break; case MSG_ID_ALPHA_NOTIFY: CatLog.d(this, "Received CAT CC Alpha message from card"); if (msg.obj != null) { AsyncResult ar = (AsyncResult) msg.obj; if (ar != null && ar.result != null) { broadcastAlphaMessage((String)ar.result); } else { CatLog.d(this, "CAT Alpha message: ar.result is null"); } } else { CatLog.d(this, "CAT Alpha message: msg.obj is null"); } break; default: throw new AssertionError("Unrecognized CAT command: " + msg.what); } } /** ** This function sends a CARD status (ABSENT, PRESENT, REFRESH) to STK_APP. ** This is triggered during ICC_REFRESH or CARD STATE changes. In case ** REFRESH, additional information is sent in 'refresh_result' ** **/ private void broadcastCardStateAndIccRefreshResp(CardState cardState, IccRefreshResponse iccRefreshState) { Intent intent = new Intent(AppInterface.CAT_ICC_STATUS_CHANGE); boolean cardPresent = (cardState == CardState.CARDSTATE_PRESENT); if (iccRefreshState != null) { //This case is when MSG_ID_ICC_REFRESH is received. intent.putExtra(AppInterface.REFRESH_RESULT, iccRefreshState.refreshResult); CatLog.d(this, "Sending IccResult with Result: " + iccRefreshState.refreshResult); } // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true). intent.putExtra(AppInterface.CARD_STATUS, cardPresent); CatLog.d(this, "Sending Card Status: " + cardState + " " + "cardPresent: " + cardPresent); mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); } private void broadcastAlphaMessage(String alphaString) { CatLog.d(this, "Broadcasting CAT Alpha message from card: " + alphaString); Intent intent = new Intent(AppInterface.CAT_ALPHA_NOTIFY_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(AppInterface.ALPHA_STRING, alphaString); intent.putExtra("SLOT_ID", mSlotId); mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION); } @Override public synchronized void onCmdResponse(CatResponseMessage resMsg) { if (resMsg == null) { return; } // queue a response message. Message msg = obtainMessage(MSG_ID_RESPONSE, resMsg); msg.sendToTarget(); } private boolean validateResponse(CatResponseMessage resMsg) { boolean validResponse = false; if ((resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_EVENT_LIST.value()) || (resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_MENU.value())) { CatLog.d(this, "CmdType: " + resMsg.mCmdDet.typeOfCommand); validResponse = true; } else if (mCurrntCmd != null) { validResponse = resMsg.mCmdDet.compareTo(mCurrntCmd.mCmdDet); CatLog.d(this, "isResponse for last valid cmd: " + validResponse); } return validResponse; } private boolean removeMenu(Menu menu) { try { if (menu.items.size() == 1 && menu.items.get(0) == null) { return true; } } catch (NullPointerException e) { CatLog.d(this, "Unable to get Menu's items size"); return true; } return false; } private void handleCmdResponse(CatResponseMessage resMsg) { // Make sure the response details match the last valid command. An invalid // response is a one that doesn't have a corresponding proactive command // and sending it can "confuse" the baseband/ril. // One reason for out of order responses can be UI glitches. For example, // if the application launch an activity, and that activity is stored // by the framework inside the history stack. That activity will be // available for relaunch using the latest application dialog // (long press on the home button). Relaunching that activity can send // the same command's result again to the CatService and can cause it to // get out of sync with the SIM. This can happen in case of // non-interactive type Setup Event List and SETUP_MENU proactive commands. // Stk framework would have already sent Terminal Response to Setup Event // List and SETUP_MENU proactive commands. After sometime Stk app will send // Envelope Command/Event Download. In which case, the response details doesn't // match with last valid command (which are not related). // However, we should allow Stk framework to send the message to ICC. if (!validateResponse(resMsg)) { return; } ResponseData resp = null; boolean helpRequired = false; CommandDetails cmdDet = resMsg.getCmdDetails(); AppInterface.CommandType type = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand); switch (resMsg.mResCode) { case HELP_INFO_REQUIRED: helpRequired = true; // fall through case OK: case PRFRMD_WITH_PARTIAL_COMPREHENSION: case PRFRMD_WITH_MISSING_INFO: case PRFRMD_WITH_ADDITIONAL_EFS_READ: case PRFRMD_ICON_NOT_DISPLAYED: case PRFRMD_MODIFIED_BY_NAA: case PRFRMD_LIMITED_SERVICE: case PRFRMD_WITH_MODIFICATION: case PRFRMD_NAA_NOT_ACTIVE: case PRFRMD_TONE_NOT_PLAYED: case LAUNCH_BROWSER_ERROR: case TERMINAL_CRNTLY_UNABLE_TO_PROCESS: switch (type) { case SET_UP_MENU: helpRequired = resMsg.mResCode == ResultCode.HELP_INFO_REQUIRED; sendMenuSelection(resMsg.mUsersMenuSelection, helpRequired); return; case SELECT_ITEM: resp = new SelectItemResponseData(resMsg.mUsersMenuSelection); break; case GET_INPUT: case GET_INKEY: Input input = mCurrntCmd.geInput(); if (!input.yesNo) { // when help is requested there is no need to send the text // string object. if (!helpRequired) { resp = new GetInkeyInputResponseData(resMsg.mUsersInput, input.ucs2, input.packed); } } else { resp = new GetInkeyInputResponseData( resMsg.mUsersYesNoSelection); } break; case DISPLAY_TEXT: if (resMsg.mResCode == ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS) { // For screenbusy case there will be addtional information in the terminal // response. And the value of the additional information byte is 0x01. resMsg.setAdditionalInfo(0x01); } else { resMsg.mIncludeAdditionalInfo = false; resMsg.mAdditionalInfo = 0; } break; case LAUNCH_BROWSER: break; // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR case OPEN_CHANNEL: case SET_UP_CALL: mCmdIf.handleCallSetupRequestFromSim(resMsg.mUsersConfirm, null); // No need to send terminal response for SET UP CALL. The user's // confirmation result is send back using a dedicated ril message // invoked by the CommandInterface call above. mCurrntCmd = null; return; case SET_UP_EVENT_LIST: if (IDLE_SCREEN_AVAILABLE_EVENT == resMsg.mEventValue) { eventDownload(resMsg.mEventValue, DEV_ID_DISPLAY, DEV_ID_UICC, resMsg.mAddedInfo, false); } else { eventDownload(resMsg.mEventValue, DEV_ID_TERMINAL, DEV_ID_UICC, resMsg.mAddedInfo, false); } // No need to send the terminal response after event download. return; default: break; } break; case BACKWARD_MOVE_BY_USER: case USER_NOT_ACCEPT: // if the user dismissed the alert dialog for a // setup call/open channel, consider that as the user // rejecting the call. Use dedicated API for this, rather than // sending a terminal response. if (type == CommandType.SET_UP_CALL || type == CommandType.OPEN_CHANNEL) { mCmdIf.handleCallSetupRequestFromSim(false, null); mCurrntCmd = null; return; } else { resp = null; } break; case NO_RESPONSE_FROM_USER: case UICC_SESSION_TERM_BY_USER: resp = null; break; default: return; } sendTerminalResponse(cmdDet, resMsg.mResCode, resMsg.mIncludeAdditionalInfo, resMsg.mAdditionalInfo, resp); mCurrntCmd = null; } private boolean isStkAppInstalled() { Intent intent = new Intent(AppInterface.CAT_CMD_ACTION); PackageManager pm = mContext.getPackageManager(); List broadcastReceivers = pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA); int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size(); return (numReceiver > 0); } public void update(CommandsInterface ci, Context context, UiccCard ic) { UiccCardApplication ca = null; IccRecords ir = null; if (ic != null) { /* Since Cat is not tied to any application, but rather is Uicc application * in itself - just get first FileHandler and IccRecords object */ ca = ic.getApplicationIndex(0); if (ca != null) { ir = ca.getIccRecords(); } } synchronized (sInstanceLock) { if ((ir != null) && (mIccRecords != ir)) { if (mIccRecords != null) { mIccRecords.unregisterForRecordsLoaded(this); } CatLog.d(this, "Reinitialize the Service with SIMRecords and UiccCardApplication"); mIccRecords = ir; mUiccApplication = ca; // re-Register for SIM ready event. mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null); CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this); } } } void updateIccAvailability() { if (null == mUiccController) { return; } CardState newState = CardState.CARDSTATE_ABSENT; UiccCard newCard = mUiccController.getUiccCard(mSlotId); if (newCard != null) { newState = newCard.getCardState(); } CardState oldState = mCardState; mCardState = newState; CatLog.d(this,"New Card State = " + newState + " " + "Old Card State = " + oldState); if (oldState == CardState.CARDSTATE_PRESENT && newState != CardState.CARDSTATE_PRESENT) { broadcastCardStateAndIccRefreshResp(newState, null); } else if (oldState != CardState.CARDSTATE_PRESENT && newState == CardState.CARDSTATE_PRESENT) { // Card moved to PRESENT STATE. mCmdIf.reportStkServiceIsRunning(null); } } }