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.gsm.stk;
18
19import android.content.Context;
20import android.content.Intent;
21import android.os.AsyncResult;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Message;
25
26import com.android.internal.telephony.IccUtils;
27import com.android.internal.telephony.CommandsInterface;
28import com.android.internal.telephony.gsm.SimCard;
29import com.android.internal.telephony.gsm.SIMFileHandler;
30import com.android.internal.telephony.gsm.SIMRecords;
31
32import android.util.Config;
33
34import java.io.ByteArrayOutputStream;
35
36/**
37 * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
38 * you want to get the actual value, call {@link #value() value} method.
39 *
40 * {@hide}
41 */
42enum ComprehensionTlvTag {
43  COMMAND_DETAILS(0x01),
44  DEVICE_IDENTITIES(0x02),
45  RESULT(0x03),
46  DURATION(0x04),
47  ALPHA_ID(0x05),
48  USSD_STRING(0x0a),
49  TEXT_STRING(0x0d),
50  TONE(0x0e),
51  ITEM(0x0f),
52  ITEM_ID(0x10),
53  RESPONSE_LENGTH(0x11),
54  FILE_LIST(0x12),
55  HELP_REQUEST(0x15),
56  DEFAULT_TEXT(0x17),
57  EVENT_LIST(0x19),
58  ICON_ID(0x1e),
59  ITEM_ICON_ID_LIST(0x1f),
60  IMMEDIATE_RESPONSE(0x2b),
61  LANGUAGE(0x2d),
62  URL(0x31),
63  BROWSER_TERMINATION_CAUSE(0x34),
64  TEXT_ATTRIBUTE(0x50);
65
66    private int mValue;
67
68    ComprehensionTlvTag(int value) {
69        mValue = value;
70    }
71
72    /**
73     * Returns the actual value of this COMPREHENSION-TLV object.
74     *
75     * @return Actual tag value of this object
76     */
77        public int value() {
78            return mValue;
79        }
80
81    public static ComprehensionTlvTag fromInt(int value) {
82        for (ComprehensionTlvTag e : ComprehensionTlvTag.values()) {
83            if (e.mValue == value) {
84                return e;
85            }
86        }
87        return null;
88    }
89}
90
91class RilMessage {
92    int mId;
93    Object mData;
94    ResultCode mResCode;
95
96    RilMessage(int msgId, String rawData) {
97        mId = msgId;
98        mData = rawData;
99    }
100
101    RilMessage(RilMessage other) {
102        this.mId = other.mId;
103        this.mData = other.mData;
104        this.mResCode = other.mResCode;
105    }
106}
107
108/**
109 * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
110 * and application.
111 *
112 * {@hide}
113 */
114public class StkService extends Handler implements AppInterface {
115
116    // Class members
117    private static SIMRecords mSimRecords;
118
119    // Service members.
120    private static StkService sInstance;
121    private CommandsInterface mCmdIf;
122    private Context mContext;
123    private StkCmdMessage mCurrntCmd = null;
124    private StkCmdMessage mMenuCmd = null;
125
126    private RilMessageDecoder mMsgDecoder = null;
127
128    // Service constants.
129    static final int MSG_ID_SESSION_END              = 1;
130    static final int MSG_ID_PROACTIVE_COMMAND        = 2;
131    static final int MSG_ID_EVENT_NOTIFY             = 3;
132    static final int MSG_ID_CALL_SETUP               = 4;
133    static final int MSG_ID_REFRESH                  = 5;
134    static final int MSG_ID_RESPONSE                 = 6;
135
136    static final int MSG_ID_RIL_MSG_DECODED          = 10;
137
138    // Events to signal SIM presence or absent in the device.
139    private static final int MSG_ID_SIM_LOADED       = 20;
140
141    private static final int DEV_ID_KEYPAD      = 0x01;
142    private static final int DEV_ID_DISPLAY     = 0x02;
143    private static final int DEV_ID_EARPIECE    = 0x03;
144    private static final int DEV_ID_UICC        = 0x81;
145    private static final int DEV_ID_TERMINAL    = 0x82;
146    private static final int DEV_ID_NETWORK     = 0x83;
147
148    /* Intentionally private for singleton */
149    private StkService(CommandsInterface ci, SIMRecords sr, Context context,
150            SIMFileHandler fh, SimCard sc) {
151        if (ci == null || sr == null || context == null || fh == null
152                || sc == null) {
153            throw new NullPointerException(
154                    "Service: Input parameters must not be null");
155        }
156        mCmdIf = ci;
157        mContext = context;
158
159        // Get the RilMessagesDecoder for decoding the messages.
160        mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
161
162        // Register ril events handling.
163        mCmdIf.setOnStkSessionEnd(this, MSG_ID_SESSION_END, null);
164        mCmdIf.setOnStkProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
165        mCmdIf.setOnStkEvent(this, MSG_ID_EVENT_NOTIFY, null);
166        mCmdIf.setOnStkCallSetUp(this, MSG_ID_CALL_SETUP, null);
167        //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
168
169        mSimRecords = sr;
170
171        // Register for SIM ready event.
172        mSimRecords.registerForRecordsLoaded(this, MSG_ID_SIM_LOADED, null);
173
174        mCmdIf.reportStkServiceIsRunning(null);
175        StkLog.d(this, "StkService: is running");
176    }
177
178    public void dispose() {
179        mSimRecords.unregisterForRecordsLoaded(this);
180        mCmdIf.unSetOnStkSessionEnd(this);
181        mCmdIf.unSetOnStkProactiveCmd(this);
182        mCmdIf.unSetOnStkEvent(this);
183        mCmdIf.unSetOnStkCallSetUp(this);
184
185        this.removeCallbacksAndMessages(null);
186    }
187
188    protected void finalize() {
189        StkLog.d(this, "Service finalized");
190    }
191
192    private void handleRilMsg(RilMessage rilMsg) {
193        if (rilMsg == null) {
194            return;
195        }
196
197        // dispatch messages
198        CommandParams cmdParams = null;
199        switch (rilMsg.mId) {
200        case MSG_ID_EVENT_NOTIFY:
201            if (rilMsg.mResCode == ResultCode.OK) {
202                cmdParams = (CommandParams) rilMsg.mData;
203                if (cmdParams != null) {
204                    handleProactiveCommand(cmdParams);
205                }
206            }
207            break;
208        case MSG_ID_PROACTIVE_COMMAND:
209            cmdParams = (CommandParams) rilMsg.mData;
210            if (cmdParams != null) {
211                if (rilMsg.mResCode == ResultCode.OK) {
212                    handleProactiveCommand(cmdParams);
213                } else {
214                    // for proactive commands that couldn't be decoded
215                    // successfully respond with the code generated by the
216                    // message decoder.
217                    sendTerminalResponse(cmdParams.cmdDet, rilMsg.mResCode,
218                            false, 0, null);
219                }
220            }
221            break;
222        case MSG_ID_REFRESH:
223            cmdParams = (CommandParams) rilMsg.mData;
224            if (cmdParams != null) {
225                handleProactiveCommand(cmdParams);
226            }
227            break;
228        case MSG_ID_SESSION_END:
229            handleSessionEnd();
230            break;
231        case MSG_ID_CALL_SETUP:
232            // prior event notify command supplied all the information
233            // needed for set up call processing.
234            break;
235        }
236    }
237
238    /**
239     * Handles RIL_UNSOL_STK_PROACTIVE_COMMAND unsolicited command from RIL.
240     * Sends valid proactive command data to the application using intents.
241     *
242     */
243    private void handleProactiveCommand(CommandParams cmdParams) {
244        StkLog.d(this, cmdParams.getCommandType().name());
245
246        StkCmdMessage cmdMsg = new StkCmdMessage(cmdParams);
247        switch (cmdParams.getCommandType()) {
248        case SET_UP_MENU:
249            if (removeMenu(cmdMsg.getMenu())) {
250                mMenuCmd = null;
251            } else {
252                mMenuCmd = cmdMsg;
253            }
254            sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false, 0,
255                    null);
256            break;
257        case DISPLAY_TEXT:
258            // when application is not required to respond, send an immediate
259            // response.
260            if (!cmdMsg.geTextMessage().responseNeeded) {
261                sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false,
262                        0, null);
263            }
264            break;
265        case REFRESH:
266            // ME side only handles refresh commands which meant to remove IDLE
267            // MODE TEXT.
268            cmdParams.cmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT
269                    .value();
270            break;
271        case SET_UP_IDLE_MODE_TEXT:
272            sendTerminalResponse(cmdParams.cmdDet, ResultCode.OK, false,
273                    0, null);
274            break;
275        case LAUNCH_BROWSER:
276        case SELECT_ITEM:
277        case GET_INPUT:
278        case GET_INKEY:
279        case SEND_DTMF:
280        case SEND_SMS:
281        case SEND_SS:
282        case SEND_USSD:
283        case PLAY_TONE:
284        case SET_UP_CALL:
285            // nothing to do on telephony!
286            break;
287        default:
288            StkLog.d(this, "Unsupported command");
289            return;
290        }
291        mCurrntCmd = cmdMsg;
292        Intent intent = new Intent(AppInterface.STK_CMD_ACTION);
293        intent.putExtra("STK CMD", cmdMsg);
294        mContext.sendBroadcast(intent);
295    }
296
297    /**
298     * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
299     *
300     */
301    private void handleSessionEnd() {
302        StkLog.d(this, "SESSION END");
303
304        mCurrntCmd = mMenuCmd;
305        Intent intent = new Intent(AppInterface.STK_SESSION_END_ACTION);
306        mContext.sendBroadcast(intent);
307    }
308
309    private void sendTerminalResponse(CommandDetails cmdDet,
310            ResultCode resultCode, boolean includeAdditionalInfo,
311            int additionalInfo, ResponseData resp) {
312
313        if (cmdDet == null) {
314            return;
315        }
316        ByteArrayOutputStream buf = new ByteArrayOutputStream();
317
318        // command details
319        int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
320        if (cmdDet.compRequired) {
321            tag |= 0x80;
322        }
323        buf.write(tag);
324        buf.write(0x03); // length
325        buf.write(cmdDet.commandNumber);
326        buf.write(cmdDet.typeOfCommand);
327        buf.write(cmdDet.commandQualifier);
328
329        // device identities
330        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
331        buf.write(tag);
332        buf.write(0x02); // length
333        buf.write(DEV_ID_TERMINAL); // source device id
334        buf.write(DEV_ID_UICC); // destination device id
335
336        // result
337        tag = 0x80 | ComprehensionTlvTag.RESULT.value();
338        buf.write(tag);
339        int length = includeAdditionalInfo ? 2 : 1;
340        buf.write(length);
341        buf.write(resultCode.value());
342
343        // additional info
344        if (includeAdditionalInfo) {
345            buf.write(additionalInfo);
346        }
347
348        // Fill optional data for each corresponding command
349        if (resp != null) {
350            resp.format(buf);
351        }
352
353        byte[] rawData = buf.toByteArray();
354        String hexString = IccUtils.bytesToHexString(rawData);
355        if (Config.LOGD) {
356            StkLog.d(this, "TERMINAL RESPONSE: " + hexString);
357        }
358
359        mCmdIf.sendTerminalResponse(hexString, null);
360    }
361
362
363    private void sendMenuSelection(int menuId, boolean helpRequired) {
364
365        ByteArrayOutputStream buf = new ByteArrayOutputStream();
366
367        // tag
368        int tag = BerTlv.BER_MENU_SELECTION_TAG;
369        buf.write(tag);
370
371        // length
372        buf.write(0x00); // place holder
373
374        // device identities
375        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
376        buf.write(tag);
377        buf.write(0x02); // length
378        buf.write(DEV_ID_KEYPAD); // source device id
379        buf.write(DEV_ID_UICC); // destination device id
380
381        // item identifier
382        tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
383        buf.write(tag);
384        buf.write(0x01); // length
385        buf.write(menuId); // menu identifier chosen
386
387        // help request
388        if (helpRequired) {
389            tag = ComprehensionTlvTag.HELP_REQUEST.value();
390            buf.write(tag);
391            buf.write(0x00); // length
392        }
393
394        byte[] rawData = buf.toByteArray();
395
396        // write real length
397        int len = rawData.length - 2; // minus (tag + length)
398        rawData[1] = (byte) len;
399
400        String hexString = IccUtils.bytesToHexString(rawData);
401
402        mCmdIf.sendEnvelope(hexString, null);
403    }
404
405    private void eventDownload(int event, int sourceId, int destinationId,
406            byte[] additionalInfo, boolean oneShot) {
407
408        ByteArrayOutputStream buf = new ByteArrayOutputStream();
409
410        // tag
411        int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
412        buf.write(tag);
413
414        // length
415        buf.write(0x00); // place holder, assume length < 128.
416
417        // event list
418        tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
419        buf.write(tag);
420        buf.write(0x01); // length
421        buf.write(event); // event value
422
423        // device identities
424        tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
425        buf.write(tag);
426        buf.write(0x02); // length
427        buf.write(sourceId); // source device id
428        buf.write(destinationId); // destination device id
429
430        // additional information
431        if (additionalInfo != null) {
432            for (byte b : additionalInfo) {
433                buf.write(b);
434            }
435        }
436
437        byte[] rawData = buf.toByteArray();
438
439        // write real length
440        int len = rawData.length - 2; // minus (tag + length)
441        rawData[1] = (byte) len;
442
443        String hexString = IccUtils.bytesToHexString(rawData);
444
445        mCmdIf.sendEnvelope(hexString, null);
446    }
447
448    /**
449     * Used for instantiating/updating the Service from the GsmPhone constructor.
450     *
451     * @param ci CommandsInterface object
452     * @param sr SIMRecords object
453     * @param context phone app context
454     * @param fh SIM file handler
455     * @param sc GSM SIM card
456     * @return The only Service object in the system
457     */
458    public static StkService getInstance(CommandsInterface ci, SIMRecords sr,
459            Context context, SIMFileHandler fh, SimCard sc) {
460        if (sInstance == null) {
461            if (ci == null || sr == null || context == null || fh == null
462                    || sc == null) {
463                return null;
464            }
465            HandlerThread thread = new HandlerThread("Stk Telephony service");
466            thread.start();
467            sInstance = new StkService(ci, sr, context, fh, sc);
468            StkLog.d(sInstance, "NEW sInstance");
469        } else if ((sr != null) && (mSimRecords != sr)) {
470            StkLog.d(sInstance, String.format(
471                    "Reinitialize the Service with SIMRecords sr=0x%x.", sr));
472            mSimRecords = sr;
473
474            // re-Register for SIM ready event.
475            mSimRecords.registerForRecordsLoaded(sInstance, MSG_ID_SIM_LOADED, null);
476            StkLog.d(sInstance, "sr changed reinitialize and return current sInstance");
477        } else {
478            StkLog.d(sInstance, "Return current sInstance");
479        }
480        return sInstance;
481    }
482
483    /**
484     * Used by application to get an AppInterface object.
485     *
486     * @return The only Service object in the system
487     */
488    public static AppInterface getInstance() {
489        return getInstance(null, null, null, null, null);
490    }
491
492    @Override
493    public void handleMessage(Message msg) {
494
495        switch (msg.what) {
496        case MSG_ID_SESSION_END:
497        case MSG_ID_PROACTIVE_COMMAND:
498        case MSG_ID_EVENT_NOTIFY:
499        case MSG_ID_REFRESH:
500            StkLog.d(this, "ril message arrived");
501            String data = null;
502            if (msg.obj != null) {
503                AsyncResult ar = (AsyncResult) msg.obj;
504                if (ar != null && ar.result != null) {
505                    try {
506                        data = (String) ar.result;
507                    } catch (ClassCastException e) {
508                        break;
509                    }
510                }
511            }
512            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
513            break;
514        case MSG_ID_CALL_SETUP:
515            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
516            break;
517        case MSG_ID_SIM_LOADED:
518            break;
519        case MSG_ID_RIL_MSG_DECODED:
520            handleRilMsg((RilMessage) msg.obj);
521            break;
522        case MSG_ID_RESPONSE:
523            handleCmdResponse((StkResponseMessage) msg.obj);
524            break;
525        default:
526            throw new AssertionError("Unrecognized STK command: " + msg.what);
527        }
528    }
529
530    public synchronized void onCmdResponse(StkResponseMessage resMsg) {
531        if (resMsg == null) {
532            return;
533        }
534        // queue a response message.
535        Message msg = this.obtainMessage(MSG_ID_RESPONSE, resMsg);
536        msg.sendToTarget();
537    }
538
539    private boolean validateResponse(StkResponseMessage resMsg) {
540        if (mCurrntCmd != null) {
541            return (resMsg.cmdDet.compareTo(mCurrntCmd.mCmdDet));
542        }
543        return false;
544    }
545
546    private boolean removeMenu(Menu menu) {
547        try {
548            if (menu.items.size() == 1 && menu.items.get(0) == null) {
549                return true;
550            }
551        } catch (NullPointerException e) {
552            StkLog.d(this, "Unable to get Menu's items size");
553            return true;
554        }
555        return false;
556    }
557
558    private void handleCmdResponse(StkResponseMessage resMsg) {
559        // Make sure the response details match the last valid command. An invalid
560        // response is a one that doesn't have a corresponding proactive command
561        // and sending it can "confuse" the baseband/ril.
562        // One reason for out of order responses can be UI glitches. For example,
563        // if the application launch an activity, and that activity is stored
564        // by the framework inside the history stack. That activity will be
565        // available for relaunch using the latest application dialog
566        // (long press on the home button). Relaunching that activity can send
567        // the same command's result again to the StkService and can cause it to
568        // get out of sync with the SIM.
569        if (!validateResponse(resMsg)) {
570            return;
571        }
572        ResponseData resp = null;
573        boolean helpRequired = false;
574        CommandDetails cmdDet = resMsg.getCmdDetails();
575
576        switch (resMsg.resCode) {
577        case HELP_INFO_REQUIRED:
578            helpRequired = true;
579            // fall through
580        case OK:
581        case PRFRMD_WITH_PARTIAL_COMPREHENSION:
582        case PRFRMD_WITH_MISSING_INFO:
583        case PRFRMD_WITH_ADDITIONAL_EFS_READ:
584        case PRFRMD_ICON_NOT_DISPLAYED:
585        case PRFRMD_MODIFIED_BY_NAA:
586        case PRFRMD_LIMITED_SERVICE:
587        case PRFRMD_WITH_MODIFICATION:
588        case PRFRMD_NAA_NOT_ACTIVE:
589        case PRFRMD_TONE_NOT_PLAYED:
590            switch (AppInterface.CommandType.fromInt(cmdDet.typeOfCommand)) {
591            case SET_UP_MENU:
592                helpRequired = resMsg.resCode == ResultCode.HELP_INFO_REQUIRED;
593                sendMenuSelection(resMsg.usersMenuSelection, helpRequired);
594                return;
595            case SELECT_ITEM:
596                resp = new SelectItemResponseData(resMsg.usersMenuSelection);
597                break;
598            case GET_INPUT:
599            case GET_INKEY:
600                Input input = mCurrntCmd.geInput();
601                if (!input.yesNo) {
602                    // when help is requested there is no need to send the text
603                    // string object.
604                    if (!helpRequired) {
605                        resp = new GetInkeyInputResponseData(resMsg.usersInput,
606                                input.ucs2, input.packed);
607                    }
608                } else {
609                    resp = new GetInkeyInputResponseData(
610                            resMsg.usersYesNoSelection);
611                }
612                break;
613            case DISPLAY_TEXT:
614            case LAUNCH_BROWSER:
615                break;
616            case SET_UP_CALL:
617                mCmdIf.handleCallSetupRequestFromSim(resMsg.usersConfirm, null);
618                // No need to send terminal response for SET UP CALL. The user's
619                // confirmation result is send back using a dedicated ril message
620                // invoked by the CommandInterface call above.
621                mCurrntCmd = null;
622                return;
623            }
624            break;
625        case NO_RESPONSE_FROM_USER:
626        case UICC_SESSION_TERM_BY_USER:
627        case BACKWARD_MOVE_BY_USER:
628            resp = null;
629            break;
630        default:
631            return;
632        }
633        sendTerminalResponse(cmdDet, resMsg.resCode, false, 0, resp);
634        mCurrntCmd = null;
635    }
636}
637