1/*
2 * Copyright (C) 2007 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.cat;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
23import android.content.res.Resources.NotFoundException;
24import android.os.AsyncResult;
25import android.os.Handler;
26import android.os.HandlerThread;
27import android.os.Message;
28import android.os.SystemProperties;
29import android.telephony.SubscriptionManager;
30import android.telephony.TelephonyManager;
31
32import com.android.internal.telephony.CommandsInterface;
33import com.android.internal.telephony.PhoneConstants;
34import com.android.internal.telephony.SubscriptionController;
35import com.android.internal.telephony.uicc.IccFileHandler;
36import com.android.internal.telephony.uicc.IccRecords;
37import com.android.internal.telephony.uicc.IccUtils;
38import com.android.internal.telephony.uicc.UiccCard;
39import com.android.internal.telephony.uicc.UiccCardApplication;
40import com.android.internal.telephony.uicc.IccCardStatus.CardState;
41import com.android.internal.telephony.uicc.IccRefreshResponse;
42import com.android.internal.telephony.uicc.UiccController;
43
44import java.io.ByteArrayOutputStream;
45import java.util.List;
46import java.util.Locale;
47
48import static com.android.internal.telephony.cat.CatCmdMessage.
49                   SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
50import static com.android.internal.telephony.cat.CatCmdMessage.
51                   SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
52
53class RilMessage {
54    int mId;
55    Object mData;
56    ResultCode mResCode;
57
58    RilMessage(int msgId, String rawData) {
59        mId = msgId;
60        mData = rawData;
61    }
62
63    RilMessage(RilMessage other) {
64        mId = other.mId;
65        mData = other.mData;
66        mResCode = other.mResCode;
67    }
68}
69
70/**
71 * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
72 * and application.
73 *
74 * {@hide}
75 */
76public class CatService extends Handler implements AppInterface {
77    private static final boolean DBG = false;
78
79    // Class members
80    private static IccRecords mIccRecords;
81    private static UiccCardApplication mUiccApplication;
82
83    // Service members.
84    // Protects singleton instance lazy initialization.
85    private static final Object sInstanceLock = new Object();
86    private static CatService[] sInstance = null;
87    private CommandsInterface mCmdIf;
88    private Context mContext;
89    private CatCmdMessage mCurrntCmd = null;
90    private CatCmdMessage mMenuCmd = null;
91
92    private RilMessageDecoder mMsgDecoder = null;
93    private boolean mStkAppInstalled = false;
94
95    private UiccController mUiccController;
96    private CardState mCardState = CardState.CARDSTATE_ABSENT;
97
98    // Service constants.
99    protected static final int MSG_ID_SESSION_END              = 1;
100    protected static final int MSG_ID_PROACTIVE_COMMAND        = 2;
101    protected static final int MSG_ID_EVENT_NOTIFY             = 3;
102    protected static final int MSG_ID_CALL_SETUP               = 4;
103    static final int MSG_ID_REFRESH                  = 5;
104    static final int MSG_ID_RESPONSE                 = 6;
105    static final int MSG_ID_SIM_READY                = 7;
106
107    protected static final int MSG_ID_ICC_CHANGED    = 8;
108    protected static final int MSG_ID_ALPHA_NOTIFY   = 9;
109
110    static final int MSG_ID_RIL_MSG_DECODED          = 10;
111
112    // Events to signal SIM presence or absent in the device.
113    private static final int MSG_ID_ICC_RECORDS_LOADED       = 20;
114
115    //Events to signal SIM REFRESH notificatations
116    private static final int MSG_ID_ICC_REFRESH  = 30;
117
118    private static final int DEV_ID_KEYPAD      = 0x01;
119    private static final int DEV_ID_DISPLAY     = 0x02;
120    private static final int DEV_ID_UICC        = 0x81;
121    private static final int DEV_ID_TERMINAL    = 0x82;
122    private static final int DEV_ID_NETWORK     = 0x83;
123
124    static final String STK_DEFAULT = "Default Message";
125
126    private HandlerThread mHandlerThread;
127    private int mSlotId;
128
129    /* For multisim catservice should not be singleton */
130    private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir,
131            Context context, IccFileHandler fh, UiccCard ic, int slotId) {
132        if (ci == null || ca == null || ir == null || context == null || fh == null
133                || ic == null) {
134            throw new NullPointerException(
135                    "Service: Input parameters must not be null");
136        }
137        mCmdIf = ci;
138        mContext = context;
139        mSlotId = slotId;
140        mHandlerThread = new HandlerThread("Cat Telephony service" + slotId);
141        mHandlerThread.start();
142
143        // Get the RilMessagesDecoder for decoding the messages.
144        mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId);
145        if (null == mMsgDecoder) {
146            CatLog.d(this, "Null RilMessageDecoder instance");
147            return;
148        }
149        mMsgDecoder.start();
150
151        // Register ril events handling.
152        mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
153        mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
154        mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
155        mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
156        //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
157
158        mCmdIf.registerForIccRefresh(this, MSG_ID_ICC_REFRESH, null);
159        mCmdIf.setOnCatCcAlphaNotify(this, MSG_ID_ALPHA_NOTIFY, null);
160
161        mIccRecords = ir;
162        mUiccApplication = ca;
163
164        // Register for SIM ready event.
165        mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
166        CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this);
167
168
169        mUiccController = UiccController.getInstance();
170        mUiccController.registerForIccChanged(this, MSG_ID_ICC_CHANGED, null);
171
172        // Check if STK application is available
173        mStkAppInstalled = isStkAppInstalled();
174
175        CatLog.d(this, "Running CAT service on Slotid: " + mSlotId +
176                ". STK app installed:" + mStkAppInstalled);
177    }
178
179    /**
180     * Used for instantiating the Service from the Card.
181     *
182     * @param ci CommandsInterface object
183     * @param context phone app context
184     * @param ic Icc card
185     * @param slotId to know the index of card
186     * @return The only Service object in the system
187     */
188    public static CatService getInstance(CommandsInterface ci,
189            Context context, UiccCard ic, int slotId) {
190        UiccCardApplication ca = null;
191        IccFileHandler fh = null;
192        IccRecords ir = null;
193        if (ic != null) {
194            /* Since Cat is not tied to any application, but rather is Uicc application
195             * in itself - just get first FileHandler and IccRecords object
196             */
197            ca = ic.getApplicationIndex(0);
198            if (ca != null) {
199                fh = ca.getIccFileHandler();
200                ir = ca.getIccRecords();
201            }
202        }
203
204        synchronized (sInstanceLock) {
205            if (sInstance == null) {
206                int simCount = TelephonyManager.getDefault().getSimCount();
207                sInstance = new CatService[simCount];
208                for (int i = 0; i < simCount; i++) {
209                    sInstance[i] = null;
210                }
211            }
212            if (sInstance[slotId] == null) {
213                if (ci == null || ca == null || ir == null || context == null || fh == null
214                        || ic == null) {
215                    return null;
216                }
217
218                sInstance[slotId] = new CatService(ci, ca, ir, context, fh, ic, slotId);
219            } else if ((ir != null) && (mIccRecords != ir)) {
220                if (mIccRecords != null) {
221                    mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]);
222                }
223
224                mIccRecords = ir;
225                mUiccApplication = ca;
226
227                mIccRecords.registerForRecordsLoaded(sInstance[slotId],
228                        MSG_ID_ICC_RECORDS_LOADED, null);
229                CatLog.d(sInstance[slotId], "registerForRecordsLoaded slotid=" + slotId
230                        + " instance:" + sInstance[slotId]);
231            }
232            return sInstance[slotId];
233        }
234    }
235
236    public void dispose() {
237        synchronized (sInstanceLock) {
238            CatLog.d(this, "Disposing CatService object");
239            mIccRecords.unregisterForRecordsLoaded(this);
240
241            // Clean up stk icon if dispose is called
242            broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_ABSENT, null);
243
244            mCmdIf.unSetOnCatSessionEnd(this);
245            mCmdIf.unSetOnCatProactiveCmd(this);
246            mCmdIf.unSetOnCatEvent(this);
247            mCmdIf.unSetOnCatCallSetUp(this);
248            mCmdIf.unSetOnCatCcAlphaNotify(this);
249
250            mCmdIf.unregisterForIccRefresh(this);
251            if (mUiccController != null) {
252                mUiccController.unregisterForIccChanged(this);
253                mUiccController = null;
254            }
255            mMsgDecoder.dispose();
256            mMsgDecoder = null;
257            mHandlerThread.quit();
258            mHandlerThread = null;
259            removeCallbacksAndMessages(null);
260            if (sInstance != null) {
261                if (SubscriptionManager.isValidSlotIndex(mSlotId)) {
262                    sInstance[mSlotId] = null;
263                } else {
264                    CatLog.d(this, "error: invaild slot id: " + mSlotId);
265                }
266            }
267        }
268    }
269
270    @Override
271    protected void finalize() {
272        CatLog.d(this, "Service finalized");
273    }
274
275    private void handleRilMsg(RilMessage rilMsg) {
276        if (rilMsg == null) {
277            return;
278        }
279
280        // dispatch messages
281        CommandParams cmdParams = null;
282        switch (rilMsg.mId) {
283        case MSG_ID_EVENT_NOTIFY:
284            if (rilMsg.mResCode == ResultCode.OK) {
285                cmdParams = (CommandParams) rilMsg.mData;
286                if (cmdParams != null) {
287                    handleCommand(cmdParams, false);
288                }
289            }
290            break;
291        case MSG_ID_PROACTIVE_COMMAND:
292            try {
293                cmdParams = (CommandParams) rilMsg.mData;
294            } catch (ClassCastException e) {
295                // for error handling : cast exception
296                CatLog.d(this, "Fail to parse proactive command");
297                // Don't send Terminal Resp if command detail is not available
298                if (mCurrntCmd != null) {
299                    sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD,
300                                     false, 0x00, null);
301                }
302                break;
303            }
304            if (cmdParams != null) {
305                if (rilMsg.mResCode == ResultCode.OK) {
306                    handleCommand(cmdParams, true);
307                } else {
308                    // for proactive commands that couldn't be decoded
309                    // successfully respond with the code generated by the
310                    // message decoder.
311                    sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode,
312                            false, 0, null);
313                }
314            }
315            break;
316        case MSG_ID_REFRESH:
317            cmdParams = (CommandParams) rilMsg.mData;
318            if (cmdParams != null) {
319                handleCommand(cmdParams, false);
320            }
321            break;
322        case MSG_ID_SESSION_END:
323            handleSessionEnd();
324            break;
325        case MSG_ID_CALL_SETUP:
326            // prior event notify command supplied all the information
327            // needed for set up call processing.
328            break;
329        }
330    }
331
332    /**
333     * This function validates the events in SETUP_EVENT_LIST which are currently
334     * supported by the Android framework. In case of SETUP_EVENT_LIST has NULL events
335     * or no events, all the events need to be reset.
336     */
337    private boolean isSupportedSetupEventCommand(CatCmdMessage cmdMsg) {
338        boolean flag = true;
339
340        for (int eventVal: cmdMsg.getSetEventList().eventList) {
341            CatLog.d(this,"Event: " + eventVal);
342            switch (eventVal) {
343                /* Currently android is supporting only the below events in SetupEventList
344                 * Language Selection.  */
345                case IDLE_SCREEN_AVAILABLE_EVENT:
346                case LANGUAGE_SELECTION_EVENT:
347                    break;
348                default:
349                    flag = false;
350            }
351        }
352        return flag;
353    }
354
355    /**
356     * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command
357     * from RIL.
358     * Sends valid proactive command data to the application using intents.
359     * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is
360     * from RIL_UNSOL_STK_PROACTIVE_COMMAND.
361     */
362    private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) {
363        CatLog.d(this, cmdParams.getCommandType().name());
364
365        // Log all proactive commands.
366        if (isProactiveCmd) {
367            if (mUiccController != null) {
368                mUiccController.addCardLog("ProactiveCommand mSlotId=" + mSlotId +
369                        " cmdParams=" + cmdParams);
370            }
371        }
372
373        CharSequence message;
374        ResultCode resultCode;
375        CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
376        switch (cmdParams.getCommandType()) {
377            case SET_UP_MENU:
378                if (removeMenu(cmdMsg.getMenu())) {
379                    mMenuCmd = null;
380                } else {
381                    mMenuCmd = cmdMsg;
382                }
383                resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED
384                                                                            : ResultCode.OK;
385                sendTerminalResponse(cmdParams.mCmdDet, resultCode, false, 0, null);
386                break;
387            case DISPLAY_TEXT:
388                break;
389            case REFRESH:
390                // ME side only handles refresh commands which meant to remove IDLE
391                // MODE TEXT.
392                cmdParams.mCmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value();
393                break;
394            case SET_UP_IDLE_MODE_TEXT:
395                resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED
396                                                                            : ResultCode.OK;
397                sendTerminalResponse(cmdParams.mCmdDet,resultCode, false, 0, null);
398                break;
399            case SET_UP_EVENT_LIST:
400                if (isSupportedSetupEventCommand(cmdMsg)) {
401                    sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
402                } else {
403                    sendTerminalResponse(cmdParams.mCmdDet, ResultCode.BEYOND_TERMINAL_CAPABILITY,
404                            false, 0, null);
405                }
406                break;
407            case PROVIDE_LOCAL_INFORMATION:
408                ResponseData resp;
409                switch (cmdParams.mCmdDet.commandQualifier) {
410                    case CommandParamsFactory.DTTZ_SETTING:
411                        resp = new DTTZResponseData(null);
412                        sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp);
413                        break;
414                    case CommandParamsFactory.LANGUAGE_SETTING:
415                        resp = new LanguageResponseData(Locale.getDefault().getLanguage());
416                        sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp);
417                        break;
418                    default:
419                        sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
420                }
421                // No need to start STK app here.
422                return;
423            case LAUNCH_BROWSER:
424                if ((((LaunchBrowserParams) cmdParams).mConfirmMsg.text != null)
425                        && (((LaunchBrowserParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) {
426                    message = mContext.getText(com.android.internal.R.string.launchBrowserDefault);
427                    ((LaunchBrowserParams) cmdParams).mConfirmMsg.text = message.toString();
428                }
429                break;
430            case SELECT_ITEM:
431            case GET_INPUT:
432            case GET_INKEY:
433                break;
434            case SEND_DTMF:
435            case SEND_SMS:
436            case SEND_SS:
437            case SEND_USSD:
438                if ((((DisplayTextParams)cmdParams).mTextMsg.text != null)
439                        && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) {
440                    message = mContext.getText(com.android.internal.R.string.sending);
441                    ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString();
442                }
443                break;
444            case PLAY_TONE:
445                break;
446            case SET_UP_CALL:
447                if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null)
448                        && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) {
449                    message = mContext.getText(com.android.internal.R.string.SetupCallDefault);
450                    ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
451                }
452                break;
453            case OPEN_CHANNEL:
454            case CLOSE_CHANNEL:
455            case RECEIVE_DATA:
456            case SEND_DATA:
457                BIPClientParams cmd = (BIPClientParams) cmdParams;
458                /* Per 3GPP specification 102.223,
459                 * if the alpha identifier is not provided by the UICC,
460                 * the terminal MAY give information to the user
461                 * noAlphaUsrCnf defines if you need to show user confirmation or not
462                 */
463                boolean noAlphaUsrCnf = false;
464                try {
465                    noAlphaUsrCnf = mContext.getResources().getBoolean(
466                            com.android.internal.R.bool.config_stkNoAlphaUsrCnf);
467                } catch (NotFoundException e) {
468                    noAlphaUsrCnf = false;
469                }
470                if ((cmd.mTextMsg.text == null) && (cmd.mHasAlphaId || noAlphaUsrCnf)) {
471                    CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id");
472                    // If alpha length is zero, we just respond with OK.
473                    if (isProactiveCmd) {
474                        sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
475                    } else if (cmdParams.getCommandType() == CommandType.OPEN_CHANNEL) {
476                        mCmdIf.handleCallSetupRequestFromSim(true, null);
477                    }
478                    return;
479                }
480                // Respond with permanent failure to avoid retry if STK app is not present.
481                if (!mStkAppInstalled) {
482                    CatLog.d(this, "No STK application found.");
483                    if (isProactiveCmd) {
484                        sendTerminalResponse(cmdParams.mCmdDet,
485                                             ResultCode.BEYOND_TERMINAL_CAPABILITY,
486                                             false, 0, null);
487                        return;
488                    }
489                }
490                /*
491                 * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by
492                 * either PROACTIVE_COMMAND or EVENT_NOTIFY.
493                 * If PROACTIVE_COMMAND is used for those commands, send terminal
494                 * response here.
495                 */
496                if (isProactiveCmd &&
497                    ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) ||
498                     (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) ||
499                     (cmdParams.getCommandType() == CommandType.SEND_DATA))) {
500                    sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
501                }
502                break;
503            default:
504                CatLog.d(this, "Unsupported command");
505                return;
506        }
507        mCurrntCmd = cmdMsg;
508        broadcastCatCmdIntent(cmdMsg);
509    }
510
511
512    private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) {
513        Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
514        intent.putExtra("STK CMD", cmdMsg);
515        intent.putExtra("SLOT_ID", mSlotId);
516        intent.setComponent(AppInterface.getDefaultSTKApplication());
517        CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId);
518        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
519    }
520
521    /**
522     * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
523     *
524     */
525    private void handleSessionEnd() {
526        CatLog.d(this, "SESSION END on "+ mSlotId);
527
528        mCurrntCmd = mMenuCmd;
529        Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
530        intent.putExtra("SLOT_ID", mSlotId);
531        intent.setComponent(AppInterface.getDefaultSTKApplication());
532        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
533    }
534
535
536    private void sendTerminalResponse(CommandDetails cmdDet,
537            ResultCode resultCode, boolean includeAdditionalInfo,
538            int additionalInfo, ResponseData resp) {
539
540        if (cmdDet == null) {
541            return;
542        }
543        ByteArrayOutputStream buf = new ByteArrayOutputStream();
544
545        Input cmdInput = null;
546        if (mCurrntCmd != null) {
547            cmdInput = mCurrntCmd.geInput();
548        }
549
550        // command details
551        int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
552        if (cmdDet.compRequired) {
553            tag |= 0x80;
554        }
555        buf.write(tag);
556        buf.write(0x03); // length
557        buf.write(cmdDet.commandNumber);
558        buf.write(cmdDet.typeOfCommand);
559        buf.write(cmdDet.commandQualifier);
560
561        // device identities
562        // According to TS102.223/TS31.111 section 6.8 Structure of
563        // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
564        // the ME should set the CR(comprehension required) flag to
565        // comprehension not required.(CR=0)"
566        // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
567        // the CR flag is not set.
568        tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value();
569        buf.write(tag);
570        buf.write(0x02); // length
571        buf.write(DEV_ID_TERMINAL); // source device id
572        buf.write(DEV_ID_UICC); // destination device id
573
574        // result
575        tag = ComprehensionTlvTag.RESULT.value();
576        if (cmdDet.compRequired) {
577            tag |= 0x80;
578        }
579        buf.write(tag);
580        int length = includeAdditionalInfo ? 2 : 1;
581        buf.write(length);
582        buf.write(resultCode.value());
583
584        // additional info
585        if (includeAdditionalInfo) {
586            buf.write(additionalInfo);
587        }
588
589        // Fill optional data for each corresponding command
590        if (resp != null) {
591            resp.format(buf);
592        } else {
593            encodeOptionalTags(cmdDet, resultCode, cmdInput, buf);
594        }
595
596        byte[] rawData = buf.toByteArray();
597        String hexString = IccUtils.bytesToHexString(rawData);
598        if (DBG) {
599            CatLog.d(this, "TERMINAL RESPONSE: " + hexString);
600        }
601
602        mCmdIf.sendTerminalResponse(hexString, null);
603    }
604
605    private void encodeOptionalTags(CommandDetails cmdDet,
606            ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) {
607        CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
608        if (cmdType != null) {
609            switch (cmdType) {
610                case GET_INKEY:
611                    // ETSI TS 102 384,27.22.4.2.8.4.2.
612                    // If it is a response for GET_INKEY command and the response timeout
613                    // occured, then add DURATION TLV for variable timeout case.
614                    if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) &&
615                        (cmdInput != null) && (cmdInput.duration != null)) {
616                        getInKeyResponse(buf, cmdInput);
617                    }
618                    break;
619                case PROVIDE_LOCAL_INFORMATION:
620                    if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) &&
621                        (resultCode.value() == ResultCode.OK.value())) {
622                        getPliResponse(buf);
623                    }
624                    break;
625                default:
626                    CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet);
627                    break;
628            }
629        } else {
630            CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet);
631        }
632    }
633
634    private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) {
635        int tag = ComprehensionTlvTag.DURATION.value();
636
637        buf.write(tag);
638        buf.write(0x02); // length
639        buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds)
640        buf.write(cmdInput.duration.timeInterval); // Time Duration
641    }
642
643    private void getPliResponse(ByteArrayOutputStream buf) {
644        // Locale Language Setting
645        final String lang = Locale.getDefault().getLanguage();
646
647        if (lang != null) {
648            // tag
649            int tag = ComprehensionTlvTag.LANGUAGE.value();
650            buf.write(tag);
651            ResponseData.writeLength(buf, lang.length());
652            buf.write(lang.getBytes(), 0, lang.length());
653        }
654    }
655
656    private void sendMenuSelection(int menuId, boolean helpRequired) {
657
658        ByteArrayOutputStream buf = new ByteArrayOutputStream();
659
660        // tag
661        int tag = BerTlv.BER_MENU_SELECTION_TAG;
662        buf.write(tag);
663
664        // length
665        buf.write(0x00); // place holder
666
667        // device identities
668        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
669        buf.write(tag);
670        buf.write(0x02); // length
671        buf.write(DEV_ID_KEYPAD); // source device id
672        buf.write(DEV_ID_UICC); // destination device id
673
674        // item identifier
675        tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
676        buf.write(tag);
677        buf.write(0x01); // length
678        buf.write(menuId); // menu identifier chosen
679
680        // help request
681        if (helpRequired) {
682            tag = ComprehensionTlvTag.HELP_REQUEST.value();
683            buf.write(tag);
684            buf.write(0x00); // length
685        }
686
687        byte[] rawData = buf.toByteArray();
688
689        // write real length
690        int len = rawData.length - 2; // minus (tag + length)
691        rawData[1] = (byte) len;
692
693        String hexString = IccUtils.bytesToHexString(rawData);
694
695        mCmdIf.sendEnvelope(hexString, null);
696    }
697
698    private void eventDownload(int event, int sourceId, int destinationId,
699            byte[] additionalInfo, boolean oneShot) {
700
701        ByteArrayOutputStream buf = new ByteArrayOutputStream();
702
703        // tag
704        int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
705        buf.write(tag);
706
707        // length
708        buf.write(0x00); // place holder, assume length < 128.
709
710        // event list
711        tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
712        buf.write(tag);
713        buf.write(0x01); // length
714        buf.write(event); // event value
715
716        // device identities
717        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
718        buf.write(tag);
719        buf.write(0x02); // length
720        buf.write(sourceId); // source device id
721        buf.write(destinationId); // destination device id
722
723        /*
724         * Check for type of event download to be sent to UICC - Browser
725         * termination,Idle screen available, User activity, Language selection
726         * etc as mentioned under ETSI TS 102 223 section 7.5
727         */
728
729        /*
730         * Currently the below events are supported:
731         * Language Selection Event.
732         * Other event download commands should be encoded similar way
733         */
734        /* TODO: eventDownload should be extended for other Envelope Commands */
735        switch (event) {
736            case IDLE_SCREEN_AVAILABLE_EVENT:
737                CatLog.d(sInstance, " Sending Idle Screen Available event download to ICC");
738                break;
739            case LANGUAGE_SELECTION_EVENT:
740                CatLog.d(sInstance, " Sending Language Selection event download to ICC");
741                tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
742                buf.write(tag);
743                // Language length should be 2 byte
744                buf.write(0x02);
745                break;
746            default:
747                break;
748        }
749
750        // additional information
751        if (additionalInfo != null) {
752            for (byte b : additionalInfo) {
753                buf.write(b);
754            }
755        }
756
757        byte[] rawData = buf.toByteArray();
758
759        // write real length
760        int len = rawData.length - 2; // minus (tag + length)
761        rawData[1] = (byte) len;
762
763        String hexString = IccUtils.bytesToHexString(rawData);
764
765        CatLog.d(this, "ENVELOPE COMMAND: " + hexString);
766
767        mCmdIf.sendEnvelope(hexString, null);
768    }
769
770    /**
771     * Used by application to get an AppInterface object.
772     *
773     * @return The only Service object in the system
774     */
775    //TODO Need to take care for MSIM
776    public static AppInterface getInstance() {
777        int slotId = PhoneConstants.DEFAULT_CARD_INDEX;
778        SubscriptionController sControl = SubscriptionController.getInstance();
779        if (sControl != null) {
780            slotId = sControl.getSlotIndex(sControl.getDefaultSubId());
781        }
782        return getInstance(null, null, null, slotId);
783    }
784
785    /**
786     * Used by application to get an AppInterface object.
787     *
788     * @return The only Service object in the system
789     */
790    public static AppInterface getInstance(int slotId) {
791        return getInstance(null, null, null, slotId);
792    }
793
794    @Override
795    public void handleMessage(Message msg) {
796        CatLog.d(this, "handleMessage[" + msg.what + "]");
797
798        switch (msg.what) {
799        case MSG_ID_SESSION_END:
800        case MSG_ID_PROACTIVE_COMMAND:
801        case MSG_ID_EVENT_NOTIFY:
802        case MSG_ID_REFRESH:
803            CatLog.d(this, "ril message arrived,slotid:" + mSlotId);
804            String data = null;
805            if (msg.obj != null) {
806                AsyncResult ar = (AsyncResult) msg.obj;
807                if (ar != null && ar.result != null) {
808                    try {
809                        data = (String) ar.result;
810                    } catch (ClassCastException e) {
811                        break;
812                    }
813                }
814            }
815            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
816            break;
817        case MSG_ID_CALL_SETUP:
818            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
819            break;
820        case MSG_ID_ICC_RECORDS_LOADED:
821            break;
822        case MSG_ID_RIL_MSG_DECODED:
823            handleRilMsg((RilMessage) msg.obj);
824            break;
825        case MSG_ID_RESPONSE:
826            handleCmdResponse((CatResponseMessage) msg.obj);
827            break;
828        case MSG_ID_ICC_CHANGED:
829            CatLog.d(this, "MSG_ID_ICC_CHANGED");
830            updateIccAvailability();
831            break;
832        case MSG_ID_ICC_REFRESH:
833            if (msg.obj != null) {
834                AsyncResult ar = (AsyncResult) msg.obj;
835                if (ar != null && ar.result != null) {
836                    broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_PRESENT,
837                                  (IccRefreshResponse) ar.result);
838                } else {
839                    CatLog.d(this,"Icc REFRESH with exception: " + ar.exception);
840                }
841            } else {
842                CatLog.d(this, "IccRefresh Message is null");
843            }
844            break;
845        case MSG_ID_ALPHA_NOTIFY:
846            CatLog.d(this, "Received CAT CC Alpha message from card");
847            if (msg.obj != null) {
848                AsyncResult ar = (AsyncResult) msg.obj;
849                if (ar != null && ar.result != null) {
850                    broadcastAlphaMessage((String)ar.result);
851                } else {
852                    CatLog.d(this, "CAT Alpha message: ar.result is null");
853                }
854            } else {
855                CatLog.d(this, "CAT Alpha message: msg.obj is null");
856            }
857            break;
858        default:
859            throw new AssertionError("Unrecognized CAT command: " + msg.what);
860        }
861    }
862
863    /**
864     ** This function sends a CARD status (ABSENT, PRESENT, REFRESH) to STK_APP.
865     ** This is triggered during ICC_REFRESH or CARD STATE changes. In case
866     ** REFRESH, additional information is sent in 'refresh_result'
867     **
868     **/
869    private void  broadcastCardStateAndIccRefreshResp(CardState cardState,
870            IccRefreshResponse iccRefreshState) {
871        Intent intent = new Intent(AppInterface.CAT_ICC_STATUS_CHANGE);
872        boolean cardPresent = (cardState == CardState.CARDSTATE_PRESENT);
873
874        if (iccRefreshState != null) {
875            //This case is when MSG_ID_ICC_REFRESH is received.
876            intent.putExtra(AppInterface.REFRESH_RESULT, iccRefreshState.refreshResult);
877            CatLog.d(this, "Sending IccResult with Result: "
878                    + iccRefreshState.refreshResult);
879        }
880
881        // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true).
882        intent.putExtra(AppInterface.CARD_STATUS, cardPresent);
883        intent.setComponent(AppInterface.getDefaultSTKApplication());
884        CatLog.d(this, "Sending Card Status: "
885                + cardState + " " + "cardPresent: " + cardPresent);
886        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
887    }
888
889    private void broadcastAlphaMessage(String alphaString) {
890        CatLog.d(this, "Broadcasting CAT Alpha message from card: " + alphaString);
891        Intent intent = new Intent(AppInterface.CAT_ALPHA_NOTIFY_ACTION);
892        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
893        intent.putExtra(AppInterface.ALPHA_STRING, alphaString);
894        intent.putExtra("SLOT_ID", mSlotId);
895        intent.setComponent(AppInterface.getDefaultSTKApplication());
896        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
897    }
898
899    @Override
900    public synchronized void onCmdResponse(CatResponseMessage resMsg) {
901        if (resMsg == null) {
902            return;
903        }
904        // queue a response message.
905        Message msg = obtainMessage(MSG_ID_RESPONSE, resMsg);
906        msg.sendToTarget();
907    }
908
909    private boolean validateResponse(CatResponseMessage resMsg) {
910        boolean validResponse = false;
911        if ((resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_EVENT_LIST.value())
912                || (resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_MENU.value())) {
913            CatLog.d(this, "CmdType: " + resMsg.mCmdDet.typeOfCommand);
914            validResponse = true;
915        } else if (mCurrntCmd != null) {
916            validResponse = resMsg.mCmdDet.compareTo(mCurrntCmd.mCmdDet);
917            CatLog.d(this, "isResponse for last valid cmd: " + validResponse);
918        }
919        return validResponse;
920    }
921
922    private boolean removeMenu(Menu menu) {
923        try {
924            if (menu.items.size() == 1 && menu.items.get(0) == null) {
925                return true;
926            }
927        } catch (NullPointerException e) {
928            CatLog.d(this, "Unable to get Menu's items size");
929            return true;
930        }
931        return false;
932    }
933
934    private void handleCmdResponse(CatResponseMessage resMsg) {
935        // Make sure the response details match the last valid command. An invalid
936        // response is a one that doesn't have a corresponding proactive command
937        // and sending it can "confuse" the baseband/ril.
938        // One reason for out of order responses can be UI glitches. For example,
939        // if the application launch an activity, and that activity is stored
940        // by the framework inside the history stack. That activity will be
941        // available for relaunch using the latest application dialog
942        // (long press on the home button). Relaunching that activity can send
943        // the same command's result again to the CatService and can cause it to
944        // get out of sync with the SIM. This can happen in case of
945        // non-interactive type Setup Event List and SETUP_MENU proactive commands.
946        // Stk framework would have already sent Terminal Response to Setup Event
947        // List and SETUP_MENU proactive commands. After sometime Stk app will send
948        // Envelope Command/Event Download. In which case, the response details doesn't
949        // match with last valid command (which are not related).
950        // However, we should allow Stk framework to send the message to ICC.
951        if (!validateResponse(resMsg)) {
952            return;
953        }
954        ResponseData resp = null;
955        boolean helpRequired = false;
956        CommandDetails cmdDet = resMsg.getCmdDetails();
957        AppInterface.CommandType type = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
958
959        switch (resMsg.mResCode) {
960        case HELP_INFO_REQUIRED:
961            helpRequired = true;
962            // fall through
963        case OK:
964        case PRFRMD_WITH_PARTIAL_COMPREHENSION:
965        case PRFRMD_WITH_MISSING_INFO:
966        case PRFRMD_WITH_ADDITIONAL_EFS_READ:
967        case PRFRMD_ICON_NOT_DISPLAYED:
968        case PRFRMD_MODIFIED_BY_NAA:
969        case PRFRMD_LIMITED_SERVICE:
970        case PRFRMD_WITH_MODIFICATION:
971        case PRFRMD_NAA_NOT_ACTIVE:
972        case PRFRMD_TONE_NOT_PLAYED:
973        case LAUNCH_BROWSER_ERROR:
974        case TERMINAL_CRNTLY_UNABLE_TO_PROCESS:
975            switch (type) {
976            case SET_UP_MENU:
977                helpRequired = resMsg.mResCode == ResultCode.HELP_INFO_REQUIRED;
978                sendMenuSelection(resMsg.mUsersMenuSelection, helpRequired);
979                return;
980            case SELECT_ITEM:
981                resp = new SelectItemResponseData(resMsg.mUsersMenuSelection);
982                break;
983            case GET_INPUT:
984            case GET_INKEY:
985                Input input = mCurrntCmd.geInput();
986                if (!input.yesNo) {
987                    // when help is requested there is no need to send the text
988                    // string object.
989                    if (!helpRequired) {
990                        resp = new GetInkeyInputResponseData(resMsg.mUsersInput,
991                                input.ucs2, input.packed);
992                    }
993                } else {
994                    resp = new GetInkeyInputResponseData(
995                            resMsg.mUsersYesNoSelection);
996                }
997                break;
998            case DISPLAY_TEXT:
999                if (resMsg.mResCode == ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS) {
1000                    // For screenbusy case there will be addtional information in the terminal
1001                    // response. And the value of the additional information byte is 0x01.
1002                    resMsg.setAdditionalInfo(0x01);
1003                } else {
1004                    resMsg.mIncludeAdditionalInfo = false;
1005                    resMsg.mAdditionalInfo = 0;
1006                }
1007                break;
1008            case LAUNCH_BROWSER:
1009                break;
1010            // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR
1011            case OPEN_CHANNEL:
1012            case SET_UP_CALL:
1013                mCmdIf.handleCallSetupRequestFromSim(resMsg.mUsersConfirm, null);
1014                // No need to send terminal response for SET UP CALL. The user's
1015                // confirmation result is send back using a dedicated ril message
1016                // invoked by the CommandInterface call above.
1017                mCurrntCmd = null;
1018                return;
1019            case SET_UP_EVENT_LIST:
1020                if (IDLE_SCREEN_AVAILABLE_EVENT == resMsg.mEventValue) {
1021                    eventDownload(resMsg.mEventValue, DEV_ID_DISPLAY, DEV_ID_UICC,
1022                            resMsg.mAddedInfo, false);
1023                 } else {
1024                     eventDownload(resMsg.mEventValue, DEV_ID_TERMINAL, DEV_ID_UICC,
1025                            resMsg.mAddedInfo, false);
1026                 }
1027                // No need to send the terminal response after event download.
1028                return;
1029            default:
1030                break;
1031            }
1032            break;
1033        case BACKWARD_MOVE_BY_USER:
1034        case USER_NOT_ACCEPT:
1035            // if the user dismissed the alert dialog for a
1036            // setup call/open channel, consider that as the user
1037            // rejecting the call. Use dedicated API for this, rather than
1038            // sending a terminal response.
1039            if (type == CommandType.SET_UP_CALL || type == CommandType.OPEN_CHANNEL) {
1040                mCmdIf.handleCallSetupRequestFromSim(false, null);
1041                mCurrntCmd = null;
1042                return;
1043            } else {
1044                resp = null;
1045            }
1046            break;
1047        case NO_RESPONSE_FROM_USER:
1048        case UICC_SESSION_TERM_BY_USER:
1049            resp = null;
1050            break;
1051        default:
1052            return;
1053        }
1054        sendTerminalResponse(cmdDet, resMsg.mResCode, resMsg.mIncludeAdditionalInfo,
1055                resMsg.mAdditionalInfo, resp);
1056        mCurrntCmd = null;
1057    }
1058
1059    private boolean isStkAppInstalled() {
1060        Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
1061        PackageManager pm = mContext.getPackageManager();
1062        List<ResolveInfo> broadcastReceivers =
1063                            pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA);
1064        int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size();
1065
1066        return (numReceiver > 0);
1067    }
1068
1069    public void update(CommandsInterface ci,
1070            Context context, UiccCard ic) {
1071        UiccCardApplication ca = null;
1072        IccRecords ir = null;
1073
1074        if (ic != null) {
1075            /* Since Cat is not tied to any application, but rather is Uicc application
1076             * in itself - just get first FileHandler and IccRecords object
1077             */
1078            ca = ic.getApplicationIndex(0);
1079            if (ca != null) {
1080                ir = ca.getIccRecords();
1081            }
1082        }
1083
1084        synchronized (sInstanceLock) {
1085            if ((ir != null) && (mIccRecords != ir)) {
1086                if (mIccRecords != null) {
1087                    mIccRecords.unregisterForRecordsLoaded(this);
1088                }
1089
1090                CatLog.d(this,
1091                        "Reinitialize the Service with SIMRecords and UiccCardApplication");
1092                mIccRecords = ir;
1093                mUiccApplication = ca;
1094
1095                // re-Register for SIM ready event.
1096                mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
1097                CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this);
1098            }
1099        }
1100    }
1101
1102    void updateIccAvailability() {
1103        if (null == mUiccController) {
1104            return;
1105        }
1106
1107        CardState newState = CardState.CARDSTATE_ABSENT;
1108        UiccCard newCard = mUiccController.getUiccCard(mSlotId);
1109        if (newCard != null) {
1110            newState = newCard.getCardState();
1111        }
1112        CardState oldState = mCardState;
1113        mCardState = newState;
1114        CatLog.d(this,"New Card State = " + newState + " " + "Old Card State = " + oldState);
1115        if (oldState == CardState.CARDSTATE_PRESENT &&
1116                newState != CardState.CARDSTATE_PRESENT) {
1117            broadcastCardStateAndIccRefreshResp(newState, null);
1118        } else if (oldState != CardState.CARDSTATE_PRESENT &&
1119                newState == CardState.CARDSTATE_PRESENT) {
1120            // Card moved to PRESENT STATE.
1121            mCmdIf.reportStkServiceIsRunning(null);
1122        }
1123    }
1124}
1125