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