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.graphics.Bitmap;
20import android.os.Handler;
21import android.os.Message;
22
23import com.android.internal.telephony.GsmAlphabet;
24import com.android.internal.telephony.uicc.IccFileHandler;
25
26import java.util.Iterator;
27import java.util.List;
28import static com.android.internal.telephony.cat.CatCmdMessage.
29                   SetupEventListConstants.USER_ACTIVITY_EVENT;
30import static com.android.internal.telephony.cat.CatCmdMessage.
31                   SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
32import static com.android.internal.telephony.cat.CatCmdMessage.
33                   SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
34import static com.android.internal.telephony.cat.CatCmdMessage.
35                   SetupEventListConstants.BROWSER_TERMINATION_EVENT;
36import static com.android.internal.telephony.cat.CatCmdMessage.
37                   SetupEventListConstants.BROWSING_STATUS_EVENT;
38/**
39 * Factory class, used for decoding raw byte arrays, received from baseband,
40 * into a CommandParams object.
41 *
42 */
43class CommandParamsFactory extends Handler {
44    private static CommandParamsFactory sInstance = null;
45    private IconLoader mIconLoader;
46    private CommandParams mCmdParams = null;
47    private int mIconLoadState = LOAD_NO_ICON;
48    private RilMessageDecoder mCaller = null;
49    private boolean mloadIcon = false;
50
51    // constants
52    static final int MSG_ID_LOAD_ICON_DONE = 1;
53
54    // loading icons state parameters.
55    static final int LOAD_NO_ICON           = 0;
56    static final int LOAD_SINGLE_ICON       = 1;
57    static final int LOAD_MULTI_ICONS       = 2;
58
59    // Command Qualifier values for refresh command
60    static final int REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE  = 0x00;
61    static final int REFRESH_NAA_INIT_AND_FILE_CHANGE       = 0x02;
62    static final int REFRESH_NAA_INIT                       = 0x03;
63    static final int REFRESH_UICC_RESET                     = 0x04;
64
65    // Command Qualifier values for PLI command
66    static final int DTTZ_SETTING                           = 0x03;
67    static final int LANGUAGE_SETTING                       = 0x04;
68
69    // As per TS 102.223 Annex C, Structure of CAT communications,
70    // the APDU length can be max 255 bytes. This leaves only 239 bytes for user
71    // input string. CMD details TLV + Device IDs TLV + Result TLV + Other
72    // details of TextString TLV not including user input take 16 bytes.
73    //
74    // If UCS2 encoding is used, maximum 118 UCS2 chars can be encoded in 238 bytes.
75    // Each UCS2 char takes 2 bytes. Byte Order Mask(BOM), 0xFEFF takes 2 bytes.
76    //
77    // If GSM 7 bit default(use 8 bits to represent a 7 bit char) format is used,
78    // maximum 239 chars can be encoded in 239 bytes since each char takes 1 byte.
79    //
80    // No issues for GSM 7 bit packed format encoding.
81
82    private static final int MAX_GSM7_DEFAULT_CHARS = 239;
83    private static final int MAX_UCS2_CHARS = 118;
84
85    static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
86            IccFileHandler fh) {
87        if (sInstance != null) {
88            return sInstance;
89        }
90        if (fh != null) {
91            return new CommandParamsFactory(caller, fh);
92        }
93        return null;
94    }
95
96    private CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh) {
97        mCaller = caller;
98        mIconLoader = IconLoader.getInstance(this, fh);
99    }
100
101    private CommandDetails processCommandDetails(List<ComprehensionTlv> ctlvs) {
102        CommandDetails cmdDet = null;
103
104        if (ctlvs != null) {
105            // Search for the Command Details object.
106            ComprehensionTlv ctlvCmdDet = searchForTag(
107                    ComprehensionTlvTag.COMMAND_DETAILS, ctlvs);
108            if (ctlvCmdDet != null) {
109                try {
110                    cmdDet = ValueParser.retrieveCommandDetails(ctlvCmdDet);
111                } catch (ResultException e) {
112                    CatLog.d(this,
113                            "processCommandDetails: Failed to procees command details e=" + e);
114                }
115            }
116        }
117        return cmdDet;
118    }
119
120    void make(BerTlv berTlv) {
121        if (berTlv == null) {
122            return;
123        }
124        // reset global state parameters.
125        mCmdParams = null;
126        mIconLoadState = LOAD_NO_ICON;
127        // only proactive command messages are processed.
128        if (berTlv.getTag() != BerTlv.BER_PROACTIVE_COMMAND_TAG) {
129            sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD);
130            return;
131        }
132        boolean cmdPending = false;
133        List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
134        // process command dtails from the tlv list.
135        CommandDetails cmdDet = processCommandDetails(ctlvs);
136        if (cmdDet == null) {
137            sendCmdParams(ResultCode.CMD_TYPE_NOT_UNDERSTOOD);
138            return;
139        }
140
141        // extract command type enumeration from the raw value stored inside
142        // the Command Details object.
143        AppInterface.CommandType cmdType = AppInterface.CommandType
144                .fromInt(cmdDet.typeOfCommand);
145        if (cmdType == null) {
146            // This PROACTIVE COMMAND is presently not handled. Hence set
147            // result code as BEYOND_TERMINAL_CAPABILITY in TR.
148            mCmdParams = new CommandParams(cmdDet);
149            sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
150            return;
151        }
152
153        // proactive command length is incorrect.
154        if (!berTlv.isLengthValid()) {
155            mCmdParams = new CommandParams(cmdDet);
156            sendCmdParams(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
157            return;
158        }
159
160        try {
161            switch (cmdType) {
162            case SET_UP_MENU:
163                cmdPending = processSelectItem(cmdDet, ctlvs);
164                break;
165            case SELECT_ITEM:
166                cmdPending = processSelectItem(cmdDet, ctlvs);
167                break;
168            case DISPLAY_TEXT:
169                cmdPending = processDisplayText(cmdDet, ctlvs);
170                break;
171             case SET_UP_IDLE_MODE_TEXT:
172                 cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
173                 break;
174             case GET_INKEY:
175                cmdPending = processGetInkey(cmdDet, ctlvs);
176                break;
177             case GET_INPUT:
178                 cmdPending = processGetInput(cmdDet, ctlvs);
179                 break;
180             case SEND_DTMF:
181             case SEND_SMS:
182             case SEND_SS:
183             case SEND_USSD:
184                 cmdPending = processEventNotify(cmdDet, ctlvs);
185                 break;
186             case GET_CHANNEL_STATUS:
187             case SET_UP_CALL:
188                 cmdPending = processSetupCall(cmdDet, ctlvs);
189                 break;
190             case REFRESH:
191                processRefresh(cmdDet, ctlvs);
192                cmdPending = false;
193                break;
194             case LAUNCH_BROWSER:
195                 cmdPending = processLaunchBrowser(cmdDet, ctlvs);
196                 break;
197             case PLAY_TONE:
198                cmdPending = processPlayTone(cmdDet, ctlvs);
199                break;
200             case SET_UP_EVENT_LIST:
201                 cmdPending = processSetUpEventList(cmdDet, ctlvs);
202                 break;
203             case PROVIDE_LOCAL_INFORMATION:
204                cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
205                break;
206             case OPEN_CHANNEL:
207             case CLOSE_CHANNEL:
208             case RECEIVE_DATA:
209             case SEND_DATA:
210                 cmdPending = processBIPClient(cmdDet, ctlvs);
211                 break;
212            default:
213                // unsupported proactive commands
214                mCmdParams = new CommandParams(cmdDet);
215                sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
216                return;
217            }
218        } catch (ResultException e) {
219            CatLog.d(this, "make: caught ResultException e=" + e);
220            mCmdParams = new CommandParams(cmdDet);
221            sendCmdParams(e.result());
222            return;
223        }
224        if (!cmdPending) {
225            sendCmdParams(ResultCode.OK);
226        }
227    }
228
229    @Override
230    public void handleMessage(Message msg) {
231        switch (msg.what) {
232        case MSG_ID_LOAD_ICON_DONE:
233            sendCmdParams(setIcons(msg.obj));
234            break;
235        }
236    }
237
238    private ResultCode setIcons(Object data) {
239        Bitmap[] icons = null;
240        int iconIndex = 0;
241
242        if (data == null) {
243            CatLog.d(this, "Optional Icon data is NULL");
244            mCmdParams.mLoadIconFailed = true;
245            mloadIcon = false;
246            /** In case of icon load fail consider the
247            ** received proactive command as valid (sending RESULT OK) as
248            ** The result code, 'PRFRMD_ICON_NOT_DISPLAYED' will be added in the
249            ** terminal response by CatService/StkAppService if needed based on
250            ** the value of mLoadIconFailed.
251            */
252            return ResultCode.OK;
253        }
254        switch(mIconLoadState) {
255        case LOAD_SINGLE_ICON:
256            mCmdParams.setIcon((Bitmap) data);
257            break;
258        case LOAD_MULTI_ICONS:
259            icons = (Bitmap[]) data;
260            // set each item icon.
261            for (Bitmap icon : icons) {
262                mCmdParams.setIcon(icon);
263                if (icon == null && mloadIcon) {
264                    CatLog.d(this, "Optional Icon data is NULL while loading multi icons");
265                    mCmdParams.mLoadIconFailed = true;
266                }
267            }
268            break;
269        }
270        return ResultCode.OK;
271    }
272
273    private void sendCmdParams(ResultCode resCode) {
274        mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
275    }
276
277    /**
278     * Search for a COMPREHENSION-TLV object with the given tag from a list
279     *
280     * @param tag A tag to search for
281     * @param ctlvs List of ComprehensionTlv objects used to search in
282     *
283     * @return A ComprehensionTlv object that has the tag value of {@code tag}.
284     *         If no object is found with the tag, null is returned.
285     */
286    private ComprehensionTlv searchForTag(ComprehensionTlvTag tag,
287            List<ComprehensionTlv> ctlvs) {
288        Iterator<ComprehensionTlv> iter = ctlvs.iterator();
289        return searchForNextTag(tag, iter);
290    }
291
292    /**
293     * Search for the next COMPREHENSION-TLV object with the given tag from a
294     * list iterated by {@code iter}. {@code iter} points to the object next to
295     * the found object when this method returns. Used for searching the same
296     * list for similar tags, usually item id.
297     *
298     * @param tag A tag to search for
299     * @param iter Iterator for ComprehensionTlv objects used for search
300     *
301     * @return A ComprehensionTlv object that has the tag value of {@code tag}.
302     *         If no object is found with the tag, null is returned.
303     */
304    private ComprehensionTlv searchForNextTag(ComprehensionTlvTag tag,
305            Iterator<ComprehensionTlv> iter) {
306        int tagValue = tag.value();
307        while (iter.hasNext()) {
308            ComprehensionTlv ctlv = iter.next();
309            if (ctlv.getTag() == tagValue) {
310                return ctlv;
311            }
312        }
313        return null;
314    }
315
316    /**
317     * Processes DISPLAY_TEXT proactive command from the SIM card.
318     *
319     * @param cmdDet Command Details container object.
320     * @param ctlvs List of ComprehensionTlv objects following Command Details
321     *        object and Device Identities object within the proactive command
322     * @return true if the command is processing is pending and additional
323     *         asynchronous processing is required.
324     * @throws ResultException
325     */
326    private boolean processDisplayText(CommandDetails cmdDet,
327            List<ComprehensionTlv> ctlvs)
328            throws ResultException {
329
330        CatLog.d(this, "process DisplayText");
331
332        TextMessage textMsg = new TextMessage();
333        IconId iconId = null;
334
335        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
336                ctlvs);
337        if (ctlv != null) {
338            textMsg.text = ValueParser.retrieveTextString(ctlv);
339        }
340        // If the tlv object doesn't exist or the it is a null object reply
341        // with command not understood.
342        if (textMsg.text == null) {
343            throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
344        }
345
346        ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
347        if (ctlv != null) {
348            textMsg.responseNeeded = false;
349        }
350        // parse icon identifier
351        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
352        if (ctlv != null) {
353            iconId = ValueParser.retrieveIconId(ctlv);
354            textMsg.iconSelfExplanatory = iconId.selfExplanatory;
355        }
356        // parse tone duration
357        ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
358        if (ctlv != null) {
359            textMsg.duration = ValueParser.retrieveDuration(ctlv);
360        }
361
362        // Parse command qualifier parameters.
363        textMsg.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
364        textMsg.userClear = (cmdDet.commandQualifier & 0x80) != 0;
365
366        mCmdParams = new DisplayTextParams(cmdDet, textMsg);
367
368        if (iconId != null) {
369            mloadIcon = true;
370            mIconLoadState = LOAD_SINGLE_ICON;
371            mIconLoader.loadIcon(iconId.recordNumber, this
372                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
373            return true;
374        }
375        return false;
376    }
377
378    /**
379     * Processes SET_UP_IDLE_MODE_TEXT proactive command from the SIM card.
380     *
381     * @param cmdDet Command Details container object.
382     * @param ctlvs List of ComprehensionTlv objects following Command Details
383     *        object and Device Identities object within the proactive command
384     * @return true if the command is processing is pending and additional
385     *         asynchronous processing is required.
386     * @throws ResultException
387     */
388    private boolean processSetUpIdleModeText(CommandDetails cmdDet,
389            List<ComprehensionTlv> ctlvs) throws ResultException {
390
391        CatLog.d(this, "process SetUpIdleModeText");
392
393        TextMessage textMsg = new TextMessage();
394        IconId iconId = null;
395
396        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
397                ctlvs);
398        if (ctlv != null) {
399            textMsg.text = ValueParser.retrieveTextString(ctlv);
400        }
401
402        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
403        if (ctlv != null) {
404            iconId = ValueParser.retrieveIconId(ctlv);
405            textMsg.iconSelfExplanatory = iconId.selfExplanatory;
406        }
407
408        /*
409         * If the tlv object doesn't contain text and the icon is not self
410         * explanatory then reply with command not understood.
411         */
412
413        if (textMsg.text == null && iconId != null && !textMsg.iconSelfExplanatory) {
414            throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
415        }
416        mCmdParams = new DisplayTextParams(cmdDet, textMsg);
417
418        if (iconId != null) {
419            mloadIcon = true;
420            mIconLoadState = LOAD_SINGLE_ICON;
421            mIconLoader.loadIcon(iconId.recordNumber, this
422                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
423            return true;
424        }
425        return false;
426    }
427
428    /**
429     * Processes GET_INKEY proactive command from the SIM card.
430     *
431     * @param cmdDet Command Details container object.
432     * @param ctlvs List of ComprehensionTlv objects following Command Details
433     *        object and Device Identities object within the proactive command
434     * @return true if the command is processing is pending and additional
435     *         asynchronous processing is required.
436     * @throws ResultException
437     */
438    private boolean processGetInkey(CommandDetails cmdDet,
439            List<ComprehensionTlv> ctlvs) throws ResultException {
440
441        CatLog.d(this, "process GetInkey");
442
443        Input input = new Input();
444        IconId iconId = null;
445
446        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
447                ctlvs);
448        if (ctlv != null) {
449            input.text = ValueParser.retrieveTextString(ctlv);
450        } else {
451            throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
452        }
453        // parse icon identifier
454        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
455        if (ctlv != null) {
456            iconId = ValueParser.retrieveIconId(ctlv);
457        }
458
459        // parse duration
460        ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
461        if (ctlv != null) {
462            input.duration = ValueParser.retrieveDuration(ctlv);
463        }
464
465        input.minLen = 1;
466        input.maxLen = 1;
467
468        input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
469        input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
470        input.yesNo = (cmdDet.commandQualifier & 0x04) != 0;
471        input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
472        input.echo = true;
473
474        mCmdParams = new GetInputParams(cmdDet, input);
475
476        if (iconId != null) {
477            mloadIcon = true;
478            mIconLoadState = LOAD_SINGLE_ICON;
479            mIconLoader.loadIcon(iconId.recordNumber, this
480                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
481            return true;
482        }
483        return false;
484    }
485
486    /**
487     * Processes GET_INPUT proactive command from the SIM card.
488     *
489     * @param cmdDet Command Details container object.
490     * @param ctlvs List of ComprehensionTlv objects following Command Details
491     *        object and Device Identities object within the proactive command
492     * @return true if the command is processing is pending and additional
493     *         asynchronous processing is required.
494     * @throws ResultException
495     */
496    private boolean processGetInput(CommandDetails cmdDet,
497            List<ComprehensionTlv> ctlvs) throws ResultException {
498
499        CatLog.d(this, "process GetInput");
500
501        Input input = new Input();
502        IconId iconId = null;
503
504        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
505                ctlvs);
506        if (ctlv != null) {
507            input.text = ValueParser.retrieveTextString(ctlv);
508        } else {
509            throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
510        }
511
512        ctlv = searchForTag(ComprehensionTlvTag.RESPONSE_LENGTH, ctlvs);
513        if (ctlv != null) {
514            try {
515                byte[] rawValue = ctlv.getRawValue();
516                int valueIndex = ctlv.getValueIndex();
517                input.minLen = rawValue[valueIndex] & 0xff;
518                input.maxLen = rawValue[valueIndex + 1] & 0xff;
519            } catch (IndexOutOfBoundsException e) {
520                throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
521            }
522        } else {
523            throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
524        }
525
526        ctlv = searchForTag(ComprehensionTlvTag.DEFAULT_TEXT, ctlvs);
527        if (ctlv != null) {
528            input.defaultText = ValueParser.retrieveTextString(ctlv);
529        }
530        // parse icon identifier
531        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
532        if (ctlv != null) {
533            iconId = ValueParser.retrieveIconId(ctlv);
534        }
535
536        input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
537        input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
538        input.echo = (cmdDet.commandQualifier & 0x04) == 0;
539        input.packed = (cmdDet.commandQualifier & 0x08) != 0;
540        input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
541
542        // Truncate the maxLen if it exceeds the max number of chars that can
543        // be encoded. Limit depends on DCS in Command Qualifier.
544        if (input.ucs2 && input.maxLen > MAX_UCS2_CHARS) {
545            CatLog.d(this, "UCS2: received maxLen = " + input.maxLen +
546                  ", truncating to " + MAX_UCS2_CHARS);
547            input.maxLen = MAX_UCS2_CHARS;
548        } else if (!input.packed && input.maxLen > MAX_GSM7_DEFAULT_CHARS) {
549            CatLog.d(this, "GSM 7Bit Default: received maxLen = " + input.maxLen +
550                  ", truncating to " + MAX_GSM7_DEFAULT_CHARS);
551            input.maxLen = MAX_GSM7_DEFAULT_CHARS;
552        }
553
554        mCmdParams = new GetInputParams(cmdDet, input);
555
556        if (iconId != null) {
557            mloadIcon = true;
558            mIconLoadState = LOAD_SINGLE_ICON;
559            mIconLoader.loadIcon(iconId.recordNumber, this
560                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
561            return true;
562        }
563        return false;
564    }
565
566    /**
567     * Processes REFRESH proactive command from the SIM card.
568     *
569     * @param cmdDet Command Details container object.
570     * @param ctlvs List of ComprehensionTlv objects following Command Details
571     *        object and Device Identities object within the proactive command
572     */
573    private boolean processRefresh(CommandDetails cmdDet,
574            List<ComprehensionTlv> ctlvs) {
575
576        CatLog.d(this, "process Refresh");
577
578        // REFRESH proactive command is rerouted by the baseband and handled by
579        // the telephony layer. IDLE TEXT should be removed for a REFRESH command
580        // with "initialization" or "reset"
581        switch (cmdDet.commandQualifier) {
582        case REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE:
583        case REFRESH_NAA_INIT_AND_FILE_CHANGE:
584        case REFRESH_NAA_INIT:
585        case REFRESH_UICC_RESET:
586            mCmdParams = new DisplayTextParams(cmdDet, null);
587            break;
588        }
589        return false;
590    }
591
592    /**
593     * Processes SELECT_ITEM proactive command from the SIM card.
594     *
595     * @param cmdDet Command Details container object.
596     * @param ctlvs List of ComprehensionTlv objects following Command Details
597     *        object and Device Identities object within the proactive command
598     * @return true if the command is processing is pending and additional
599     *         asynchronous processing is required.
600     * @throws ResultException
601     */
602    private boolean processSelectItem(CommandDetails cmdDet,
603            List<ComprehensionTlv> ctlvs) throws ResultException {
604
605        CatLog.d(this, "process SelectItem");
606
607        Menu menu = new Menu();
608        IconId titleIconId = null;
609        ItemsIconId itemsIconId = null;
610        Iterator<ComprehensionTlv> iter = ctlvs.iterator();
611
612        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
613                ctlvs);
614        if (ctlv != null) {
615            menu.title = ValueParser.retrieveAlphaId(ctlv);
616        }
617
618        while (true) {
619            ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
620            if (ctlv != null) {
621                menu.items.add(ValueParser.retrieveItem(ctlv));
622            } else {
623                break;
624            }
625        }
626
627        // We must have at least one menu item.
628        if (menu.items.size() == 0) {
629            throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
630        }
631
632        ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);
633        if (ctlv != null) {
634            // CAT items are listed 1...n while list start at 0, need to
635            // subtract one.
636            menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;
637        }
638
639        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
640        if (ctlv != null) {
641            mIconLoadState = LOAD_SINGLE_ICON;
642            titleIconId = ValueParser.retrieveIconId(ctlv);
643            menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
644        }
645
646        ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
647        if (ctlv != null) {
648            mIconLoadState = LOAD_MULTI_ICONS;
649            itemsIconId = ValueParser.retrieveItemsIconId(ctlv);
650            menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
651        }
652
653        boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
654        if (presentTypeSpecified) {
655            if ((cmdDet.commandQualifier & 0x02) == 0) {
656                menu.presentationType = PresentationType.DATA_VALUES;
657            } else {
658                menu.presentationType = PresentationType.NAVIGATION_OPTIONS;
659            }
660        }
661        menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
662        menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
663
664        mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);
665
666        // Load icons data if needed.
667        switch(mIconLoadState) {
668        case LOAD_NO_ICON:
669            return false;
670        case LOAD_SINGLE_ICON:
671            mloadIcon = true;
672            mIconLoader.loadIcon(titleIconId.recordNumber, this
673                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
674            break;
675        case LOAD_MULTI_ICONS:
676            int[] recordNumbers = itemsIconId.recordNumbers;
677            if (titleIconId != null) {
678                // Create a new array for all the icons (title and items).
679                recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
680                recordNumbers[0] = titleIconId.recordNumber;
681                System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers,
682                        1, itemsIconId.recordNumbers.length);
683            }
684            mloadIcon = true;
685            mIconLoader.loadIcons(recordNumbers, this
686                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
687            break;
688        }
689        return true;
690    }
691
692    /**
693     * Processes EVENT_NOTIFY message from baseband.
694     *
695     * @param cmdDet Command Details container object.
696     * @param ctlvs List of ComprehensionTlv objects following Command Details
697     *        object and Device Identities object within the proactive command
698     * @return true if the command is processing is pending and additional
699     *         asynchronous processing is required.
700     */
701    private boolean processEventNotify(CommandDetails cmdDet,
702            List<ComprehensionTlv> ctlvs) throws ResultException {
703
704        CatLog.d(this, "process EventNotify");
705
706        TextMessage textMsg = new TextMessage();
707        IconId iconId = null;
708
709        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
710                ctlvs);
711        textMsg.text = ValueParser.retrieveAlphaId(ctlv);
712
713        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
714        if (ctlv != null) {
715            iconId = ValueParser.retrieveIconId(ctlv);
716            textMsg.iconSelfExplanatory = iconId.selfExplanatory;
717        }
718
719        textMsg.responseNeeded = false;
720        mCmdParams = new DisplayTextParams(cmdDet, textMsg);
721
722        if (iconId != null) {
723            mloadIcon = true;
724            mIconLoadState = LOAD_SINGLE_ICON;
725            mIconLoader.loadIcon(iconId.recordNumber, this
726                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
727            return true;
728        }
729        return false;
730    }
731
732    /**
733     * Processes SET_UP_EVENT_LIST proactive command from the SIM card.
734     *
735     * @param cmdDet Command Details object retrieved.
736     * @param ctlvs List of ComprehensionTlv objects following Command Details
737     *        object and Device Identities object within the proactive command
738     * @return false. This function always returns false meaning that the command
739     *         processing is  not pending and additional asynchronous processing
740     *         is not required.
741     */
742    private boolean processSetUpEventList(CommandDetails cmdDet,
743            List<ComprehensionTlv> ctlvs) {
744
745        CatLog.d(this, "process SetUpEventList");
746        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.EVENT_LIST, ctlvs);
747        if (ctlv != null) {
748            try {
749                byte[] rawValue = ctlv.getRawValue();
750                int valueIndex = ctlv.getValueIndex();
751                int valueLen = ctlv.getLength();
752                int[] eventList = new int[valueLen];
753                int eventValue = -1;
754                int i = 0;
755                while (valueLen > 0) {
756                    eventValue = rawValue[valueIndex] & 0xff;
757                    valueIndex++;
758                    valueLen--;
759
760                    switch (eventValue) {
761                        case USER_ACTIVITY_EVENT:
762                        case IDLE_SCREEN_AVAILABLE_EVENT:
763                        case LANGUAGE_SELECTION_EVENT:
764                        case BROWSER_TERMINATION_EVENT:
765                        case BROWSING_STATUS_EVENT:
766                            eventList[i] = eventValue;
767                            i++;
768                            break;
769                        default:
770                            break;
771                    }
772
773                }
774                mCmdParams = new SetEventListParams(cmdDet, eventList);
775            } catch (IndexOutOfBoundsException e) {
776                CatLog.e(this, " IndexOutofBoundException in processSetUpEventList");
777            }
778        }
779        return false;
780    }
781
782    /**
783     * Processes LAUNCH_BROWSER proactive command from the SIM card.
784     *
785     * @param cmdDet Command Details container object.
786     * @param ctlvs List of ComprehensionTlv objects following Command Details
787     *        object and Device Identities object within the proactive command
788     * @return true if the command is processing is pending and additional
789     *         asynchronous processing is required.
790     * @throws ResultException
791     */
792    private boolean processLaunchBrowser(CommandDetails cmdDet,
793            List<ComprehensionTlv> ctlvs) throws ResultException {
794
795        CatLog.d(this, "process LaunchBrowser");
796
797        TextMessage confirmMsg = new TextMessage();
798        IconId iconId = null;
799        String url = null;
800
801        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.URL, ctlvs);
802        if (ctlv != null) {
803            try {
804                byte[] rawValue = ctlv.getRawValue();
805                int valueIndex = ctlv.getValueIndex();
806                int valueLen = ctlv.getLength();
807                if (valueLen > 0) {
808                    url = GsmAlphabet.gsm8BitUnpackedToString(rawValue,
809                            valueIndex, valueLen);
810                } else {
811                    url = null;
812                }
813            } catch (IndexOutOfBoundsException e) {
814                throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
815            }
816        }
817
818        // parse alpha identifier.
819        ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
820        confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
821
822        // parse icon identifier
823        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
824        if (ctlv != null) {
825            iconId = ValueParser.retrieveIconId(ctlv);
826            confirmMsg.iconSelfExplanatory = iconId.selfExplanatory;
827        }
828
829        // parse command qualifier value.
830        LaunchBrowserMode mode;
831        switch (cmdDet.commandQualifier) {
832        case 0x00:
833        default:
834            mode = LaunchBrowserMode.LAUNCH_IF_NOT_ALREADY_LAUNCHED;
835            break;
836        case 0x02:
837            mode = LaunchBrowserMode.USE_EXISTING_BROWSER;
838            break;
839        case 0x03:
840            mode = LaunchBrowserMode.LAUNCH_NEW_BROWSER;
841            break;
842        }
843
844        mCmdParams = new LaunchBrowserParams(cmdDet, confirmMsg, url, mode);
845
846        if (iconId != null) {
847            mIconLoadState = LOAD_SINGLE_ICON;
848            mIconLoader.loadIcon(iconId.recordNumber, this
849                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
850            return true;
851        }
852        return false;
853    }
854
855     /**
856     * Processes PLAY_TONE proactive command from the SIM card.
857     *
858     * @param cmdDet Command Details container object.
859     * @param ctlvs List of ComprehensionTlv objects following Command Details
860     *        object and Device Identities object within the proactive command
861     * @return true if the command is processing is pending and additional
862     *         asynchronous processing is required.t
863     * @throws ResultException
864     */
865    private boolean processPlayTone(CommandDetails cmdDet,
866            List<ComprehensionTlv> ctlvs) throws ResultException {
867
868        CatLog.d(this, "process PlayTone");
869
870        Tone tone = null;
871        TextMessage textMsg = new TextMessage();
872        Duration duration = null;
873        IconId iconId = null;
874
875        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TONE, ctlvs);
876        if (ctlv != null) {
877            // Nothing to do for null objects.
878            if (ctlv.getLength() > 0) {
879                try {
880                    byte[] rawValue = ctlv.getRawValue();
881                    int valueIndex = ctlv.getValueIndex();
882                    int toneVal = rawValue[valueIndex];
883                    tone = Tone.fromInt(toneVal);
884                } catch (IndexOutOfBoundsException e) {
885                    throw new ResultException(
886                            ResultCode.CMD_DATA_NOT_UNDERSTOOD);
887                }
888            }
889        }
890        // parse alpha identifier
891        ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
892        if (ctlv != null) {
893            textMsg.text = ValueParser.retrieveAlphaId(ctlv);
894        }
895        // parse tone duration
896        ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
897        if (ctlv != null) {
898            duration = ValueParser.retrieveDuration(ctlv);
899        }
900        // parse icon identifier
901        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
902        if (ctlv != null) {
903            iconId = ValueParser.retrieveIconId(ctlv);
904            textMsg.iconSelfExplanatory = iconId.selfExplanatory;
905        }
906
907        boolean vibrate = (cmdDet.commandQualifier & 0x01) != 0x00;
908
909        textMsg.responseNeeded = false;
910        mCmdParams = new PlayToneParams(cmdDet, textMsg, tone, duration, vibrate);
911
912        if (iconId != null) {
913            mIconLoadState = LOAD_SINGLE_ICON;
914            mIconLoader.loadIcon(iconId.recordNumber, this
915                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
916            return true;
917        }
918        return false;
919    }
920
921    /**
922     * Processes SETUP_CALL proactive command from the SIM card.
923     *
924     * @param cmdDet Command Details object retrieved from the proactive command
925     *        object
926     * @param ctlvs List of ComprehensionTlv objects following Command Details
927     *        object and Device Identities object within the proactive command
928     * @return true if the command is processing is pending and additional
929     *         asynchronous processing is required.
930     */
931    private boolean processSetupCall(CommandDetails cmdDet,
932            List<ComprehensionTlv> ctlvs) throws ResultException {
933        CatLog.d(this, "process SetupCall");
934
935        Iterator<ComprehensionTlv> iter = ctlvs.iterator();
936        ComprehensionTlv ctlv = null;
937        // User confirmation phase message.
938        TextMessage confirmMsg = new TextMessage();
939        // Call set up phase message.
940        TextMessage callMsg = new TextMessage();
941        IconId confirmIconId = null;
942        IconId callIconId = null;
943
944        // get confirmation message string.
945        ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
946        confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
947
948        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
949        if (ctlv != null) {
950            confirmIconId = ValueParser.retrieveIconId(ctlv);
951            confirmMsg.iconSelfExplanatory = confirmIconId.selfExplanatory;
952        }
953
954        // get call set up message string.
955        ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
956        if (ctlv != null) {
957            callMsg.text = ValueParser.retrieveAlphaId(ctlv);
958        }
959
960        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
961        if (ctlv != null) {
962            callIconId = ValueParser.retrieveIconId(ctlv);
963            callMsg.iconSelfExplanatory = callIconId.selfExplanatory;
964        }
965
966        mCmdParams = new CallSetupParams(cmdDet, confirmMsg, callMsg);
967
968        if (confirmIconId != null || callIconId != null) {
969            mIconLoadState = LOAD_MULTI_ICONS;
970            int[] recordNumbers = new int[2];
971            recordNumbers[0] = confirmIconId != null
972                    ? confirmIconId.recordNumber : -1;
973            recordNumbers[1] = callIconId != null ? callIconId.recordNumber
974                    : -1;
975
976            mIconLoader.loadIcons(recordNumbers, this
977                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
978            return true;
979        }
980        return false;
981    }
982
983    private boolean processProvideLocalInfo(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
984            throws ResultException {
985        CatLog.d(this, "process ProvideLocalInfo");
986        switch (cmdDet.commandQualifier) {
987            case DTTZ_SETTING:
988                CatLog.d(this, "PLI [DTTZ_SETTING]");
989                mCmdParams = new CommandParams(cmdDet);
990                break;
991            case LANGUAGE_SETTING:
992                CatLog.d(this, "PLI [LANGUAGE_SETTING]");
993                mCmdParams = new CommandParams(cmdDet);
994                break;
995            default:
996                CatLog.d(this, "PLI[" + cmdDet.commandQualifier + "] Command Not Supported");
997                mCmdParams = new CommandParams(cmdDet);
998                throw new ResultException(ResultCode.BEYOND_TERMINAL_CAPABILITY);
999        }
1000        return false;
1001    }
1002
1003    private boolean processBIPClient(CommandDetails cmdDet,
1004                                     List<ComprehensionTlv> ctlvs) throws ResultException {
1005        AppInterface.CommandType commandType =
1006                                    AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
1007        if (commandType != null) {
1008            CatLog.d(this, "process "+ commandType.name());
1009        }
1010
1011        TextMessage textMsg = new TextMessage();
1012        IconId iconId = null;
1013        ComprehensionTlv ctlv = null;
1014        boolean has_alpha_id = false;
1015
1016        // parse alpha identifier
1017        ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
1018        if (ctlv != null) {
1019            textMsg.text = ValueParser.retrieveAlphaId(ctlv);
1020            CatLog.d(this, "alpha TLV text=" + textMsg.text);
1021            has_alpha_id = true;
1022        }
1023
1024        // parse icon identifier
1025        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
1026        if (ctlv != null) {
1027            iconId = ValueParser.retrieveIconId(ctlv);
1028            textMsg.iconSelfExplanatory = iconId.selfExplanatory;
1029        }
1030
1031        textMsg.responseNeeded = false;
1032        mCmdParams = new BIPClientParams(cmdDet, textMsg, has_alpha_id);
1033
1034        if (iconId != null) {
1035            mIconLoadState = LOAD_SINGLE_ICON;
1036            mIconLoader.loadIcon(iconId.recordNumber, obtainMessage(MSG_ID_LOAD_ICON_DONE));
1037            return true;
1038        }
1039        return false;
1040    }
1041
1042    public void dispose() {
1043        mIconLoader.dispose();
1044        mIconLoader = null;
1045        mCmdParams = null;
1046        mCaller = null;
1047        sInstance = null;
1048    }
1049}
1050