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.os.AsyncResult;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Message;
27import android.os.SystemProperties;
28
29import com.android.internal.telephony.IccUtils;
30import com.android.internal.telephony.CommandsInterface;
31import com.android.internal.telephony.IccCard;
32import com.android.internal.telephony.IccFileHandler;
33import com.android.internal.telephony.IccRecords;
34
35
36import java.io.ByteArrayOutputStream;
37import java.util.List;
38import java.util.Locale;
39
40class RilMessage {
41    int mId;
42    Object mData;
43    ResultCode mResCode;
44
45    RilMessage(int msgId, String rawData) {
46        mId = msgId;
47        mData = rawData;
48    }
49
50    RilMessage(RilMessage other) {
51        this.mId = other.mId;
52        this.mData = other.mData;
53        this.mResCode = other.mResCode;
54    }
55}
56
57/**
58 * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
59 * and application.
60 *
61 * {@hide}
62 */
63public class CatService extends Handler implements AppInterface {
64
65    // Class members
66    private static IccRecords mIccRecords;
67
68    // Service members.
69    // Protects singleton instance lazy initialization.
70    private static final Object sInstanceLock = new Object();
71    private static CatService sInstance;
72    private CommandsInterface mCmdIf;
73    private Context mContext;
74    private CatCmdMessage mCurrntCmd = null;
75    private CatCmdMessage mMenuCmd = null;
76
77    private RilMessageDecoder mMsgDecoder = null;
78    private boolean mStkAppInstalled = false;
79
80    // Service constants.
81    static final int MSG_ID_SESSION_END              = 1;
82    static final int MSG_ID_PROACTIVE_COMMAND        = 2;
83    static final int MSG_ID_EVENT_NOTIFY             = 3;
84    static final int MSG_ID_CALL_SETUP               = 4;
85    static final int MSG_ID_REFRESH                  = 5;
86    static final int MSG_ID_RESPONSE                 = 6;
87    static final int MSG_ID_SIM_READY                = 7;
88
89    static final int MSG_ID_RIL_MSG_DECODED          = 10;
90
91    // Events to signal SIM presence or absent in the device.
92    private static final int MSG_ID_ICC_RECORDS_LOADED       = 20;
93
94    private static final int DEV_ID_KEYPAD      = 0x01;
95    private static final int DEV_ID_DISPLAY     = 0x02;
96    private static final int DEV_ID_EARPIECE    = 0x03;
97    private static final int DEV_ID_UICC        = 0x81;
98    private static final int DEV_ID_TERMINAL    = 0x82;
99    private static final int DEV_ID_NETWORK     = 0x83;
100
101    static final String STK_DEFAULT = "Defualt Message";
102
103    /* Intentionally private for singleton */
104    private CatService(CommandsInterface ci, IccRecords ir, Context context,
105            IccFileHandler fh, IccCard ic) {
106        if (ci == null || ir == null || context == null || fh == null
107                || ic == null) {
108            throw new NullPointerException(
109                    "Service: Input parameters must not be null");
110        }
111        mCmdIf = ci;
112        mContext = context;
113
114        // Get the RilMessagesDecoder for decoding the messages.
115        mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
116
117        // Register ril events handling.
118        mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
119        mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
120        mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
121        mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
122        //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
123
124        mIccRecords = ir;
125
126        // Register for SIM ready event.
127        ic.registerForReady(this, MSG_ID_SIM_READY, null);
128        mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
129
130        // Check if STK application is availalbe
131        mStkAppInstalled = isStkAppInstalled();
132
133        CatLog.d(this, "Running CAT service. STK app installed:" + mStkAppInstalled);
134    }
135
136    public void dispose() {
137        mIccRecords.unregisterForRecordsLoaded(this);
138        mCmdIf.unSetOnCatSessionEnd(this);
139        mCmdIf.unSetOnCatProactiveCmd(this);
140        mCmdIf.unSetOnCatEvent(this);
141        mCmdIf.unSetOnCatCallSetUp(this);
142
143        this.removeCallbacksAndMessages(null);
144    }
145
146    protected void finalize() {
147        CatLog.d(this, "Service finalized");
148    }
149
150    private void handleRilMsg(RilMessage rilMsg) {
151        if (rilMsg == null) {
152            return;
153        }
154
155        // dispatch messages
156        CommandParams cmdParams = null;
157        switch (rilMsg.mId) {
158        case MSG_ID_EVENT_NOTIFY:
159            if (rilMsg.mResCode == ResultCode.OK) {
160                cmdParams = (CommandParams) rilMsg.mData;
161                if (cmdParams != null) {
162                    handleCommand(cmdParams, false);
163                }
164            }
165            break;
166        case MSG_ID_PROACTIVE_COMMAND:
167            try {
168                cmdParams = (CommandParams) rilMsg.mData;
169            } catch (ClassCastException e) {
170                // for error handling : cast exception
171                CatLog.d(this, "Fail to parse proactive command");
172                sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD,
173                                     false, 0x00, null);
174                break;
175            }
176            if (cmdParams != null) {
177                if (rilMsg.mResCode == ResultCode.OK) {
178                    handleCommand(cmdParams, true);
179                } else {
180                    // for proactive commands that couldn't be decoded
181                    // successfully respond with the code generated by the
182                    // message decoder.
183                    sendTerminalResponse(cmdParams.cmdDet, rilMsg.mResCode,
184                            false, 0, null);
185                }
186            }
187            break;
188        case MSG_ID_REFRESH:
189            cmdParams = (CommandParams) rilMsg.mData;
190            if (cmdParams != null) {
191                handleCommand(cmdParams, false);
192            }
193            break;
194        case MSG_ID_SESSION_END:
195            handleSessionEnd();
196            break;
197        case MSG_ID_CALL_SETUP:
198            // prior event notify command supplied all the information
199            // needed for set up call processing.
200            break;
201        }
202    }
203
204    /**
205     * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command
206     * from RIL.
207     * Sends valid proactive command data to the application using intents.
208     * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is
209     * from RIL_UNSOL_STK_PROACTIVE_COMMAND.
210     */
211    private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) {
212        CatLog.d(this, cmdParams.getCommandType().name());
213
214        CharSequence message;
215        CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
216        switch (cmdParams.getCommandType()) {
217            case SET_UP_MENU:
218                if (removeMenu(cmdMsg.getMenu())) {
219                    mMenuCmd = null;
220                } else {
221                    mMenuCmd = cmdMsg;
222                }
223                sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
224                break;
225            case DISPLAY_TEXT:
226                // when application is not required to respond, send an immediate response.
227                if (!cmdMsg.geTextMessage().responseNeeded) {
228                    sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
229                }
230                break;
231            case REFRESH:
232                // ME side only handles refresh commands which meant to remove IDLE
233                // MODE TEXT.
234                cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value();
235                break;
236            case SET_UP_IDLE_MODE_TEXT:
237                sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
238                break;
239            case PROVIDE_LOCAL_INFORMATION:
240                ResponseData resp;
241                switch (cmdParams.cmdDet.commandQualifier) {
242                    case CommandParamsFactory.DTTZ_SETTING:
243                        resp = new DTTZResponseData(null);
244                        sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, resp);
245                        break;
246                    case CommandParamsFactory.LANGUAGE_SETTING:
247                        resp = new LanguageResponseData(Locale.getDefault().getLanguage());
248                        sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, resp);
249                        break;
250                    default:
251                        sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
252                }
253                // No need to start STK app here.
254                return;
255            case LAUNCH_BROWSER:
256                if ((((LaunchBrowserParams) cmdParams).confirmMsg.text != null)
257                        && (((LaunchBrowserParams) cmdParams).confirmMsg.text.equals(STK_DEFAULT))) {
258                    message = mContext.getText(com.android.internal.R.string.launchBrowserDefault);
259                    ((LaunchBrowserParams) cmdParams).confirmMsg.text = message.toString();
260                }
261                break;
262            case SELECT_ITEM:
263            case GET_INPUT:
264            case GET_INKEY:
265                break;
266            case SEND_DTMF:
267            case SEND_SMS:
268            case SEND_SS:
269            case SEND_USSD:
270                if ((((DisplayTextParams)cmdParams).textMsg.text != null)
271                        && (((DisplayTextParams)cmdParams).textMsg.text.equals(STK_DEFAULT))) {
272                    message = mContext.getText(com.android.internal.R.string.sending);
273                    ((DisplayTextParams)cmdParams).textMsg.text = message.toString();
274                }
275                break;
276            case PLAY_TONE:
277                break;
278            case SET_UP_CALL:
279                if ((((CallSetupParams) cmdParams).confirmMsg.text != null)
280                        && (((CallSetupParams) cmdParams).confirmMsg.text.equals(STK_DEFAULT))) {
281                    message = mContext.getText(com.android.internal.R.string.SetupCallDefault);
282                    ((CallSetupParams) cmdParams).confirmMsg.text = message.toString();
283                }
284                break;
285            case OPEN_CHANNEL:
286            case CLOSE_CHANNEL:
287            case RECEIVE_DATA:
288            case SEND_DATA:
289                BIPClientParams cmd = (BIPClientParams) cmdParams;
290                if (cmd.bHasAlphaId && (cmd.textMsg.text == null)) {
291                    CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id");
292                    // If alpha length is zero, we just respond with OK.
293                    if (isProactiveCmd) {
294                        sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
295                    }
296                    return;
297                }
298                // Respond with permanent failure to avoid retry if STK app is not present.
299                if (!mStkAppInstalled) {
300                    CatLog.d(this, "No STK application found.");
301                    if (isProactiveCmd) {
302                        sendTerminalResponse(cmdParams.cmdDet,
303                                             ResultCode.BEYOND_TERMINAL_CAPABILITY,
304                                             false, 0, null);
305                        return;
306                    }
307                }
308                /*
309                 * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by
310                 * either PROACTIVE_COMMAND or EVENT_NOTIFY.
311                 * If PROACTIVE_COMMAND is used for those commands, send terminal
312                 * response here.
313                 */
314                if (isProactiveCmd &&
315                    ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) ||
316                     (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) ||
317                     (cmdParams.getCommandType() == CommandType.SEND_DATA))) {
318                    sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0, null);
319                }
320                break;
321            default:
322                CatLog.d(this, "Unsupported command");
323                return;
324        }
325        mCurrntCmd = cmdMsg;
326        Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
327        intent.putExtra("STK CMD", cmdMsg);
328        mContext.sendBroadcast(intent);
329    }
330
331    /**
332     * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
333     *
334     */
335    private void handleSessionEnd() {
336        CatLog.d(this, "SESSION END");
337
338        mCurrntCmd = mMenuCmd;
339        Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
340        mContext.sendBroadcast(intent);
341    }
342
343    private void sendTerminalResponse(CommandDetails cmdDet,
344            ResultCode resultCode, boolean includeAdditionalInfo,
345            int additionalInfo, ResponseData resp) {
346
347        if (cmdDet == null) {
348            return;
349        }
350        ByteArrayOutputStream buf = new ByteArrayOutputStream();
351
352        Input cmdInput = null;
353        if (mCurrntCmd != null) {
354            cmdInput = mCurrntCmd.geInput();
355        }
356
357        // command details
358        int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
359        if (cmdDet.compRequired) {
360            tag |= 0x80;
361        }
362        buf.write(tag);
363        buf.write(0x03); // length
364        buf.write(cmdDet.commandNumber);
365        buf.write(cmdDet.typeOfCommand);
366        buf.write(cmdDet.commandQualifier);
367
368        // device identities
369        // According to TS102.223/TS31.111 section 6.8 Structure of
370        // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
371        // the ME should set the CR(comprehension required) flag to
372        // comprehension not required.(CR=0)"
373        // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
374        // the CR flag is not set.
375        tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value();
376        buf.write(tag);
377        buf.write(0x02); // length
378        buf.write(DEV_ID_TERMINAL); // source device id
379        buf.write(DEV_ID_UICC); // destination device id
380
381        // result
382        tag = 0x80 | ComprehensionTlvTag.RESULT.value();
383        buf.write(tag);
384        int length = includeAdditionalInfo ? 2 : 1;
385        buf.write(length);
386        buf.write(resultCode.value());
387
388        // additional info
389        if (includeAdditionalInfo) {
390            buf.write(additionalInfo);
391        }
392
393        // Fill optional data for each corresponding command
394        if (resp != null) {
395            resp.format(buf);
396        } else {
397            encodeOptionalTags(cmdDet, resultCode, cmdInput, buf);
398        }
399
400        byte[] rawData = buf.toByteArray();
401        String hexString = IccUtils.bytesToHexString(rawData);
402        if (false) {
403            CatLog.d(this, "TERMINAL RESPONSE: " + hexString);
404        }
405
406        mCmdIf.sendTerminalResponse(hexString, null);
407    }
408
409    private void encodeOptionalTags(CommandDetails cmdDet,
410            ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) {
411        CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
412        if (cmdType != null) {
413            switch (cmdType) {
414                case GET_INKEY:
415                    // ETSI TS 102 384,27.22.4.2.8.4.2.
416                    // If it is a response for GET_INKEY command and the response timeout
417                    // occured, then add DURATION TLV for variable timeout case.
418                    if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) &&
419                        (cmdInput != null) && (cmdInput.duration != null)) {
420                        getInKeyResponse(buf, cmdInput);
421                    }
422                    break;
423                case PROVIDE_LOCAL_INFORMATION:
424                    if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) &&
425                        (resultCode.value() == ResultCode.OK.value())) {
426                        getPliResponse(buf);
427                    }
428                    break;
429                default:
430                    CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet);
431                    break;
432            }
433        } else {
434            CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet);
435        }
436    }
437
438    private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) {
439        int tag = ComprehensionTlvTag.DURATION.value();
440
441        buf.write(tag);
442        buf.write(0x02); // length
443        buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds)
444        buf.write(cmdInput.duration.timeInterval); // Time Duration
445    }
446
447    private void getPliResponse(ByteArrayOutputStream buf) {
448
449        // Locale Language Setting
450        String lang = SystemProperties.get("persist.sys.language");
451
452        if (lang != null) {
453            // tag
454            int tag = ComprehensionTlvTag.LANGUAGE.value();
455            buf.write(tag);
456            ResponseData.writeLength(buf, lang.length());
457            buf.write(lang.getBytes(), 0, lang.length());
458        }
459    }
460
461    private void sendMenuSelection(int menuId, boolean helpRequired) {
462
463        ByteArrayOutputStream buf = new ByteArrayOutputStream();
464
465        // tag
466        int tag = BerTlv.BER_MENU_SELECTION_TAG;
467        buf.write(tag);
468
469        // length
470        buf.write(0x00); // place holder
471
472        // device identities
473        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
474        buf.write(tag);
475        buf.write(0x02); // length
476        buf.write(DEV_ID_KEYPAD); // source device id
477        buf.write(DEV_ID_UICC); // destination device id
478
479        // item identifier
480        tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
481        buf.write(tag);
482        buf.write(0x01); // length
483        buf.write(menuId); // menu identifier chosen
484
485        // help request
486        if (helpRequired) {
487            tag = ComprehensionTlvTag.HELP_REQUEST.value();
488            buf.write(tag);
489            buf.write(0x00); // length
490        }
491
492        byte[] rawData = buf.toByteArray();
493
494        // write real length
495        int len = rawData.length - 2; // minus (tag + length)
496        rawData[1] = (byte) len;
497
498        String hexString = IccUtils.bytesToHexString(rawData);
499
500        mCmdIf.sendEnvelope(hexString, null);
501    }
502
503    private void eventDownload(int event, int sourceId, int destinationId,
504            byte[] additionalInfo, boolean oneShot) {
505
506        ByteArrayOutputStream buf = new ByteArrayOutputStream();
507
508        // tag
509        int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
510        buf.write(tag);
511
512        // length
513        buf.write(0x00); // place holder, assume length < 128.
514
515        // event list
516        tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
517        buf.write(tag);
518        buf.write(0x01); // length
519        buf.write(event); // event value
520
521        // device identities
522        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
523        buf.write(tag);
524        buf.write(0x02); // length
525        buf.write(sourceId); // source device id
526        buf.write(destinationId); // destination device id
527
528        // additional information
529        if (additionalInfo != null) {
530            for (byte b : additionalInfo) {
531                buf.write(b);
532            }
533        }
534
535        byte[] rawData = buf.toByteArray();
536
537        // write real length
538        int len = rawData.length - 2; // minus (tag + length)
539        rawData[1] = (byte) len;
540
541        String hexString = IccUtils.bytesToHexString(rawData);
542
543        mCmdIf.sendEnvelope(hexString, null);
544    }
545
546    /**
547     * Used for instantiating/updating the Service from the GsmPhone or CdmaPhone constructor.
548     *
549     * @param ci CommandsInterface object
550     * @param ir IccRecords object
551     * @param context phone app context
552     * @param fh Icc file handler
553     * @param ic Icc card
554     * @return The only Service object in the system
555     */
556    public static CatService getInstance(CommandsInterface ci, IccRecords ir,
557            Context context, IccFileHandler fh, IccCard ic) {
558        synchronized (sInstanceLock) {
559            if (sInstance == null) {
560                if (ci == null || ir == null || context == null || fh == null
561                        || ic == null) {
562                    return null;
563                }
564                HandlerThread thread = new HandlerThread("Cat Telephony service");
565                thread.start();
566                sInstance = new CatService(ci, ir, context, fh, ic);
567                CatLog.d(sInstance, "NEW sInstance");
568            } else if ((ir != null) && (mIccRecords != ir)) {
569                CatLog.d(sInstance, "Reinitialize the Service with SIMRecords");
570                mIccRecords = ir;
571
572                // re-Register for SIM ready event.
573                mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
574                CatLog.d(sInstance, "sr changed reinitialize and return current sInstance");
575            } else {
576                CatLog.d(sInstance, "Return current sInstance");
577            }
578            return sInstance;
579        }
580    }
581
582    /**
583     * Used by application to get an AppInterface object.
584     *
585     * @return The only Service object in the system
586     */
587    public static AppInterface getInstance() {
588        return getInstance(null, null, null, null, null);
589    }
590
591    @Override
592    public void handleMessage(Message msg) {
593
594        switch (msg.what) {
595        case MSG_ID_SESSION_END:
596        case MSG_ID_PROACTIVE_COMMAND:
597        case MSG_ID_EVENT_NOTIFY:
598        case MSG_ID_REFRESH:
599            CatLog.d(this, "ril message arrived");
600            String data = null;
601            if (msg.obj != null) {
602                AsyncResult ar = (AsyncResult) msg.obj;
603                if (ar != null && ar.result != null) {
604                    try {
605                        data = (String) ar.result;
606                    } catch (ClassCastException e) {
607                        break;
608                    }
609                }
610            }
611            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
612            break;
613        case MSG_ID_CALL_SETUP:
614            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
615            break;
616        case MSG_ID_ICC_RECORDS_LOADED:
617            break;
618        case MSG_ID_RIL_MSG_DECODED:
619            handleRilMsg((RilMessage) msg.obj);
620            break;
621        case MSG_ID_RESPONSE:
622            handleCmdResponse((CatResponseMessage) msg.obj);
623            break;
624        case MSG_ID_SIM_READY:
625            CatLog.d(this, "SIM ready. Reporting STK service running now...");
626            mCmdIf.reportStkServiceIsRunning(null);
627            break;
628        default:
629            throw new AssertionError("Unrecognized CAT command: " + msg.what);
630        }
631    }
632
633    public synchronized void onCmdResponse(CatResponseMessage resMsg) {
634        if (resMsg == null) {
635            return;
636        }
637        // queue a response message.
638        Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg);
639        msg.sendToTarget();
640    }
641
642    private boolean validateResponse(CatResponseMessage resMsg) {
643        if (mCurrntCmd != null) {
644            return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet));
645        }
646        return false;
647    }
648
649    private boolean removeMenu(Menu menu) {
650        try {
651            if (menu.items.size() == 1 && menu.items.get(0) == null) {
652                return true;
653            }
654        } catch (NullPointerException e) {
655            CatLog.d(this, "Unable to get Menu's items size");
656            return true;
657        }
658        return false;
659    }
660
661    private void handleCmdResponse(CatResponseMessage resMsg) {
662        // Make sure the response details match the last valid command. An invalid
663        // response is a one that doesn't have a corresponding proactive command
664        // and sending it can "confuse" the baseband/ril.
665        // One reason for out of order responses can be UI glitches. For example,
666        // if the application launch an activity, and that activity is stored
667        // by the framework inside the history stack. That activity will be
668        // available for relaunch using the latest application dialog
669        // (long press on the home button). Relaunching that activity can send
670        // the same command's result again to the CatService and can cause it to
671        // get out of sync with the SIM.
672        if (!validateResponse(resMsg)) {
673            return;
674        }
675        ResponseData resp = null;
676        boolean helpRequired = false;
677        CommandDetails cmdDet = resMsg.getCmdDetails();
678
679        switch (resMsg.resCode) {
680        case HELP_INFO_REQUIRED:
681            helpRequired = true;
682            // fall through
683        case OK:
684        case PRFRMD_WITH_PARTIAL_COMPREHENSION:
685        case PRFRMD_WITH_MISSING_INFO:
686        case PRFRMD_WITH_ADDITIONAL_EFS_READ:
687        case PRFRMD_ICON_NOT_DISPLAYED:
688        case PRFRMD_MODIFIED_BY_NAA:
689        case PRFRMD_LIMITED_SERVICE:
690        case PRFRMD_WITH_MODIFICATION:
691        case PRFRMD_NAA_NOT_ACTIVE:
692        case PRFRMD_TONE_NOT_PLAYED:
693            switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) {
694            case SET_UP_MENU:
695                helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED;
696                sendMenuSelection(resMsg.usersMenuSelection, helpRequired);
697                return;
698            case SELECT_ITEM:
699                resp = new SelectItemResponseData(resMsg.usersMenuSelection);
700                break;
701            case GET_INPUT:
702            case GET_INKEY:
703                Input input = mCurrntCmd.geInput();
704                if (!input.yesNo) {
705                    // when help is requested there is no need to send the text
706                    // string object.
707                    if (!helpRequired) {
708                        resp = new GetInkeyInputResponseData(resMsg.usersInput,
709                                input.ucs2, input.packed);
710                    }
711                } else {
712                    resp = new GetInkeyInputResponseData(
713                            resMsg.usersYesNoSelection);
714                }
715                break;
716            case DISPLAY_TEXT:
717            case LAUNCH_BROWSER:
718                break;
719            case SET_UP_CALL:
720                mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null);
721                // No need to send terminal response for SET UP CALL. The user's
722                // confirmation result is send back using a dedicated ril message
723                // invoked by the CommandInterface call above.
724                mCurrntCmd = null;
725                return;
726            }
727            break;
728        case NO_RESPONSE_FROM_USER:
729        case UICC_SESSION_TERM_BY_USER:
730        case BACKWARD_MOVE_BY_USER:
731        case USER_NOT_ACCEPT:
732            resp = null;
733            break;
734        default:
735            return;
736        }
737        sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp);
738        mCurrntCmd = null;
739    }
740
741    private boolean isStkAppInstalled() {
742        Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
743        PackageManager pm = mContext.getPackageManager();
744        List<ResolveInfo> broadcastReceivers =
745                            pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA);
746        int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size();
747
748        return (numReceiver > 0);
749    }
750}
751