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 
17 package com.android.internal.telephony.cat;
18 
19 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.BROWSER_TERMINATION_EVENT;
20 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.BROWSING_STATUS_EVENT;
21 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
22 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
23 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT;
24 
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.graphics.Bitmap;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.text.TextUtils;
30 
31 import com.android.internal.telephony.GsmAlphabet;
32 import com.android.internal.telephony.uicc.IccFileHandler;
33 
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Locale;
37 /**
38  * Factory class, used for decoding raw byte arrays, received from baseband,
39  * into a CommandParams object.
40  *
41  */
42 class CommandParamsFactory extends Handler {
43     private static CommandParamsFactory sInstance = null;
44     @UnsupportedAppUsage
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     private String mSavedLanguage;
51     private String mRequestedLanguage;
52 
53     // constants
54     static final int MSG_ID_LOAD_ICON_DONE = 1;
55 
56     // loading icons state parameters.
57     static final int LOAD_NO_ICON           = 0;
58     static final int LOAD_SINGLE_ICON       = 1;
59     static final int LOAD_MULTI_ICONS       = 2;
60 
61     // Command Qualifier values for PLI command
62     static final int DTTZ_SETTING                           = 0x03;
63     static final int LANGUAGE_SETTING                       = 0x04;
64 
65     // Command Qualifier value for language notification command
66     static final int NON_SPECIFIC_LANGUAGE                  = 0x00;
67     static final int SPECIFIC_LANGUAGE                      = 0x01;
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 
getInstance(RilMessageDecoder caller, IccFileHandler fh)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 
CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh)96     private CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh) {
97         mCaller = caller;
98         mIconLoader = IconLoader.getInstance(this, fh);
99     }
100 
processCommandDetails(List<ComprehensionTlv> ctlvs)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 
make(BerTlv berTlv)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 REFRESH:
183              case RUN_AT:
184              case SEND_SS:
185              case SEND_USSD:
186                  cmdPending = processEventNotify(cmdDet, ctlvs);
187                  break;
188              case GET_CHANNEL_STATUS:
189              case SET_UP_CALL:
190                  cmdPending = processSetupCall(cmdDet, ctlvs);
191                  break;
192              case LAUNCH_BROWSER:
193                  cmdPending = processLaunchBrowser(cmdDet, ctlvs);
194                  break;
195              case PLAY_TONE:
196                 cmdPending = processPlayTone(cmdDet, ctlvs);
197                 break;
198              case SET_UP_EVENT_LIST:
199                  cmdPending = processSetUpEventList(cmdDet, ctlvs);
200                  break;
201              case PROVIDE_LOCAL_INFORMATION:
202                 cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
203                 break;
204              case LANGUAGE_NOTIFICATION:
205                  cmdPending = processLanguageNotification(cmdDet, ctlvs);
206                  break;
207              case OPEN_CHANNEL:
208              case CLOSE_CHANNEL:
209              case RECEIVE_DATA:
210              case SEND_DATA:
211                  cmdPending = processBIPClient(cmdDet, ctlvs);
212                  break;
213             default:
214                 // unsupported proactive commands
215                 mCmdParams = new CommandParams(cmdDet);
216                 sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
217                 return;
218             }
219         } catch (ResultException e) {
220             CatLog.d(this, "make: caught ResultException e=" + e);
221             mCmdParams = new CommandParams(cmdDet);
222             sendCmdParams(e.result());
223             return;
224         }
225         if (!cmdPending) {
226             sendCmdParams(ResultCode.OK);
227         }
228     }
229 
230     @Override
handleMessage(Message msg)231     public void handleMessage(Message msg) {
232         switch (msg.what) {
233         case MSG_ID_LOAD_ICON_DONE:
234             if (mIconLoader != null) {
235                 sendCmdParams(setIcons(msg.obj));
236             }
237             break;
238         }
239     }
240 
setIcons(Object data)241     private ResultCode setIcons(Object data) {
242         Bitmap[] icons = null;
243         int iconIndex = 0;
244 
245         if (data == null) {
246             CatLog.d(this, "Optional Icon data is NULL");
247             mCmdParams.mLoadIconFailed = true;
248             mloadIcon = false;
249             /** In case of icon load fail consider the
250             ** received proactive command as valid (sending RESULT OK) as
251             ** The result code, 'PRFRMD_ICON_NOT_DISPLAYED' will be added in the
252             ** terminal response by CatService/StkAppService if needed based on
253             ** the value of mLoadIconFailed.
254             */
255             return ResultCode.OK;
256         }
257         switch(mIconLoadState) {
258         case LOAD_SINGLE_ICON:
259             mCmdParams.setIcon((Bitmap) data);
260             break;
261         case LOAD_MULTI_ICONS:
262             icons = (Bitmap[]) data;
263             // set each item icon.
264             for (Bitmap icon : icons) {
265                 mCmdParams.setIcon(icon);
266                 if (icon == null && mloadIcon) {
267                     CatLog.d(this, "Optional Icon data is NULL while loading multi icons");
268                     mCmdParams.mLoadIconFailed = true;
269                 }
270             }
271             break;
272         }
273         return ResultCode.OK;
274     }
275 
sendCmdParams(ResultCode resCode)276     private void sendCmdParams(ResultCode resCode) {
277         mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
278     }
279 
280     /**
281      * Search for a COMPREHENSION-TLV object with the given tag from a list
282      *
283      * @param tag A tag to search for
284      * @param ctlvs List of ComprehensionTlv objects used to search in
285      *
286      * @return A ComprehensionTlv object that has the tag value of {@code tag}.
287      *         If no object is found with the tag, null is returned.
288      */
289     @UnsupportedAppUsage
searchForTag(ComprehensionTlvTag tag, List<ComprehensionTlv> ctlvs)290     private ComprehensionTlv searchForTag(ComprehensionTlvTag tag,
291             List<ComprehensionTlv> ctlvs) {
292         Iterator<ComprehensionTlv> iter = ctlvs.iterator();
293         return searchForNextTag(tag, iter);
294     }
295 
296     /**
297      * Search for the next COMPREHENSION-TLV object with the given tag from a
298      * list iterated by {@code iter}. {@code iter} points to the object next to
299      * the found object when this method returns. Used for searching the same
300      * list for similar tags, usually item id.
301      *
302      * @param tag A tag to search for
303      * @param iter Iterator for ComprehensionTlv objects used for search
304      *
305      * @return A ComprehensionTlv object that has the tag value of {@code tag}.
306      *         If no object is found with the tag, null is returned.
307      */
308     @UnsupportedAppUsage
searchForNextTag(ComprehensionTlvTag tag, Iterator<ComprehensionTlv> iter)309     private ComprehensionTlv searchForNextTag(ComprehensionTlvTag tag,
310             Iterator<ComprehensionTlv> iter) {
311         int tagValue = tag.value();
312         while (iter.hasNext()) {
313             ComprehensionTlv ctlv = iter.next();
314             if (ctlv.getTag() == tagValue) {
315                 return ctlv;
316             }
317         }
318         return null;
319     }
320 
321     /**
322      * Processes DISPLAY_TEXT proactive command from the SIM card.
323      *
324      * @param cmdDet Command Details container object.
325      * @param ctlvs List of ComprehensionTlv objects following Command Details
326      *        object and Device Identities object within the proactive command
327      * @return true if the command is processing is pending and additional
328      *         asynchronous processing is required.
329      * @throws ResultException
330      */
processDisplayText(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)331     private boolean processDisplayText(CommandDetails cmdDet,
332             List<ComprehensionTlv> ctlvs)
333             throws ResultException {
334 
335         CatLog.d(this, "process DisplayText");
336 
337         TextMessage textMsg = new TextMessage();
338         IconId iconId = null;
339 
340         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
341                 ctlvs);
342         if (ctlv != null) {
343             textMsg.text = ValueParser.retrieveTextString(ctlv);
344         }
345         // If the tlv object doesn't exist or the it is a null object reply
346         // with command not understood.
347         if (textMsg.text == null) {
348             throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
349         }
350 
351         ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
352         if (ctlv != null) {
353             textMsg.responseNeeded = false;
354         }
355         // parse icon identifier
356         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
357         if (ctlv != null) {
358             iconId = ValueParser.retrieveIconId(ctlv);
359             textMsg.iconSelfExplanatory = iconId.selfExplanatory;
360         }
361         // parse tone duration
362         ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
363         if (ctlv != null) {
364             textMsg.duration = ValueParser.retrieveDuration(ctlv);
365         }
366 
367         // Parse command qualifier parameters.
368         textMsg.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
369         textMsg.userClear = (cmdDet.commandQualifier & 0x80) != 0;
370 
371         mCmdParams = new DisplayTextParams(cmdDet, textMsg);
372 
373         if (iconId != null) {
374             mloadIcon = true;
375             mIconLoadState = LOAD_SINGLE_ICON;
376             mIconLoader.loadIcon(iconId.recordNumber, this
377                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
378             return true;
379         }
380         return false;
381     }
382 
383     /**
384      * Processes SET_UP_IDLE_MODE_TEXT proactive command from the SIM card.
385      *
386      * @param cmdDet Command Details container object.
387      * @param ctlvs List of ComprehensionTlv objects following Command Details
388      *        object and Device Identities object within the proactive command
389      * @return true if the command is processing is pending and additional
390      *         asynchronous processing is required.
391      * @throws ResultException
392      */
processSetUpIdleModeText(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)393     private boolean processSetUpIdleModeText(CommandDetails cmdDet,
394             List<ComprehensionTlv> ctlvs) throws ResultException {
395 
396         CatLog.d(this, "process SetUpIdleModeText");
397 
398         TextMessage textMsg = new TextMessage();
399         IconId iconId = null;
400 
401         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
402                 ctlvs);
403         if (ctlv != null) {
404             textMsg.text = ValueParser.retrieveTextString(ctlv);
405         }
406 
407         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
408         if (ctlv != null) {
409             iconId = ValueParser.retrieveIconId(ctlv);
410             textMsg.iconSelfExplanatory = iconId.selfExplanatory;
411         }
412 
413         /*
414          * If the tlv object doesn't contain text and the icon is not self
415          * explanatory then reply with command not understood.
416          */
417 
418         if (textMsg.text == null && iconId != null && !textMsg.iconSelfExplanatory) {
419             throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
420         }
421         mCmdParams = new DisplayTextParams(cmdDet, textMsg);
422 
423         if (iconId != null) {
424             mloadIcon = true;
425             mIconLoadState = LOAD_SINGLE_ICON;
426             mIconLoader.loadIcon(iconId.recordNumber, this
427                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
428             return true;
429         }
430         return false;
431     }
432 
433     /**
434      * Processes GET_INKEY proactive command from the SIM card.
435      *
436      * @param cmdDet Command Details container object.
437      * @param ctlvs List of ComprehensionTlv objects following Command Details
438      *        object and Device Identities object within the proactive command
439      * @return true if the command is processing is pending and additional
440      *         asynchronous processing is required.
441      * @throws ResultException
442      */
processGetInkey(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)443     private boolean processGetInkey(CommandDetails cmdDet,
444             List<ComprehensionTlv> ctlvs) throws ResultException {
445 
446         CatLog.d(this, "process GetInkey");
447 
448         Input input = new Input();
449         IconId iconId = null;
450 
451         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
452                 ctlvs);
453         if (ctlv != null) {
454             input.text = ValueParser.retrieveTextString(ctlv);
455         } else {
456             throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
457         }
458         // parse icon identifier
459         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
460         if (ctlv != null) {
461             iconId = ValueParser.retrieveIconId(ctlv);
462             input.iconSelfExplanatory = iconId.selfExplanatory;
463         }
464 
465         // parse duration
466         ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
467         if (ctlv != null) {
468             input.duration = ValueParser.retrieveDuration(ctlv);
469         }
470 
471         input.minLen = 1;
472         input.maxLen = 1;
473 
474         input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
475         input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
476         input.yesNo = (cmdDet.commandQualifier & 0x04) != 0;
477         input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
478         input.echo = true;
479 
480         mCmdParams = new GetInputParams(cmdDet, input);
481 
482         if (iconId != null) {
483             mloadIcon = true;
484             mIconLoadState = LOAD_SINGLE_ICON;
485             mIconLoader.loadIcon(iconId.recordNumber, this
486                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
487             return true;
488         }
489         return false;
490     }
491 
492     /**
493      * Processes GET_INPUT proactive command from the SIM card.
494      *
495      * @param cmdDet Command Details container object.
496      * @param ctlvs List of ComprehensionTlv objects following Command Details
497      *        object and Device Identities object within the proactive command
498      * @return true if the command is processing is pending and additional
499      *         asynchronous processing is required.
500      * @throws ResultException
501      */
processGetInput(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)502     private boolean processGetInput(CommandDetails cmdDet,
503             List<ComprehensionTlv> ctlvs) throws ResultException {
504 
505         CatLog.d(this, "process GetInput");
506 
507         Input input = new Input();
508         IconId iconId = null;
509 
510         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING,
511                 ctlvs);
512         if (ctlv != null) {
513             input.text = ValueParser.retrieveTextString(ctlv);
514         } else {
515             throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
516         }
517 
518         ctlv = searchForTag(ComprehensionTlvTag.RESPONSE_LENGTH, ctlvs);
519         if (ctlv != null) {
520             try {
521                 byte[] rawValue = ctlv.getRawValue();
522                 int valueIndex = ctlv.getValueIndex();
523                 input.minLen = rawValue[valueIndex] & 0xff;
524                 input.maxLen = rawValue[valueIndex + 1] & 0xff;
525             } catch (IndexOutOfBoundsException e) {
526                 throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
527             }
528         } else {
529             throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
530         }
531 
532         ctlv = searchForTag(ComprehensionTlvTag.DEFAULT_TEXT, ctlvs);
533         if (ctlv != null) {
534             input.defaultText = ValueParser.retrieveTextString(ctlv);
535         }
536         // parse icon identifier
537         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
538         if (ctlv != null) {
539             iconId = ValueParser.retrieveIconId(ctlv);
540             input.iconSelfExplanatory = iconId.selfExplanatory;
541         }
542 
543         ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
544         if (ctlv != null) {
545             input.duration = ValueParser.retrieveDuration(ctlv);
546         }
547 
548         input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
549         input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
550         input.echo = (cmdDet.commandQualifier & 0x04) == 0;
551         input.packed = (cmdDet.commandQualifier & 0x08) != 0;
552         input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
553 
554         // Truncate the maxLen if it exceeds the max number of chars that can
555         // be encoded. Limit depends on DCS in Command Qualifier.
556         if (input.ucs2 && input.maxLen > MAX_UCS2_CHARS) {
557             CatLog.d(this, "UCS2: received maxLen = " + input.maxLen +
558                   ", truncating to " + MAX_UCS2_CHARS);
559             input.maxLen = MAX_UCS2_CHARS;
560         } else if (!input.packed && input.maxLen > MAX_GSM7_DEFAULT_CHARS) {
561             CatLog.d(this, "GSM 7Bit Default: received maxLen = " + input.maxLen +
562                   ", truncating to " + MAX_GSM7_DEFAULT_CHARS);
563             input.maxLen = MAX_GSM7_DEFAULT_CHARS;
564         }
565 
566         mCmdParams = new GetInputParams(cmdDet, input);
567 
568         if (iconId != null) {
569             mloadIcon = true;
570             mIconLoadState = LOAD_SINGLE_ICON;
571             mIconLoader.loadIcon(iconId.recordNumber, this
572                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
573             return true;
574         }
575         return false;
576     }
577 
578     /**
579      * Processes SELECT_ITEM proactive command from the SIM card.
580      *
581      * @param cmdDet Command Details container object.
582      * @param ctlvs List of ComprehensionTlv objects following Command Details
583      *        object and Device Identities object within the proactive command
584      * @return true if the command is processing is pending and additional
585      *         asynchronous processing is required.
586      * @throws ResultException
587      */
processSelectItem(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)588     private boolean processSelectItem(CommandDetails cmdDet,
589             List<ComprehensionTlv> ctlvs) throws ResultException {
590 
591         CatLog.d(this, "process SelectItem");
592 
593         Menu menu = new Menu();
594         IconId titleIconId = null;
595         ItemsIconId itemsIconId = null;
596         Iterator<ComprehensionTlv> iter = ctlvs.iterator();
597 
598         AppInterface.CommandType cmdType = AppInterface.CommandType
599                 .fromInt(cmdDet.typeOfCommand);
600 
601         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
602                 ctlvs);
603         if (ctlv != null) {
604             menu.title = ValueParser.retrieveAlphaId(ctlv);
605         } else if (cmdType == AppInterface.CommandType.SET_UP_MENU) {
606             // According to spec ETSI TS 102 223 section 6.10.3, the
607             // Alpha ID is mandatory (and also part of minimum set of
608             // elements required) for SET_UP_MENU. If it is not received
609             // by ME, then ME should respond with "error: missing minimum
610             // information" and not "command performed successfully".
611             throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
612         }
613 
614         while (true) {
615             ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
616             if (ctlv != null) {
617                 menu.items.add(ValueParser.retrieveItem(ctlv));
618             } else {
619                 break;
620             }
621         }
622 
623         // We must have at least one menu item.
624         if (menu.items.size() == 0) {
625             throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
626         }
627 
628         ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);
629         if (ctlv != null) {
630             // CAT items are listed 1...n while list start at 0, need to
631             // subtract one.
632             menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;
633         }
634 
635         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
636         if (ctlv != null) {
637             mIconLoadState = LOAD_SINGLE_ICON;
638             titleIconId = ValueParser.retrieveIconId(ctlv);
639             menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
640         }
641 
642         ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
643         if (ctlv != null) {
644             mIconLoadState = LOAD_MULTI_ICONS;
645             itemsIconId = ValueParser.retrieveItemsIconId(ctlv);
646             menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
647         }
648 
649         boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
650         if (presentTypeSpecified) {
651             if ((cmdDet.commandQualifier & 0x02) == 0) {
652                 menu.presentationType = PresentationType.DATA_VALUES;
653             } else {
654                 menu.presentationType = PresentationType.NAVIGATION_OPTIONS;
655             }
656         }
657         menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
658         menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
659 
660         mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);
661 
662         // Load icons data if needed.
663         switch(mIconLoadState) {
664         case LOAD_NO_ICON:
665             return false;
666         case LOAD_SINGLE_ICON:
667             mloadIcon = true;
668             mIconLoader.loadIcon(titleIconId.recordNumber, this
669                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
670             break;
671         case LOAD_MULTI_ICONS:
672             int[] recordNumbers = itemsIconId.recordNumbers;
673             if (titleIconId != null) {
674                 // Create a new array for all the icons (title and items).
675                 recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
676                 recordNumbers[0] = titleIconId.recordNumber;
677                 System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers,
678                         1, itemsIconId.recordNumbers.length);
679             }
680             mloadIcon = true;
681             mIconLoader.loadIcons(recordNumbers, this
682                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
683             break;
684         }
685         return true;
686     }
687 
688     /**
689      * Processes EVENT_NOTIFY message from baseband.
690      *
691      * @param cmdDet Command Details container object.
692      * @param ctlvs List of ComprehensionTlv objects following Command Details
693      *        object and Device Identities object within the proactive command
694      * @return true if the command is processing is pending and additional
695      *         asynchronous processing is required.
696      */
processEventNotify(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)697     private boolean processEventNotify(CommandDetails cmdDet,
698             List<ComprehensionTlv> ctlvs) throws ResultException {
699 
700         CatLog.d(this, "process EventNotify");
701 
702         TextMessage textMsg = new TextMessage();
703         IconId iconId = null;
704 
705         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
706                 ctlvs);
707         textMsg.text = ValueParser.retrieveAlphaId(ctlv);
708 
709         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
710         if (ctlv != null) {
711             iconId = ValueParser.retrieveIconId(ctlv);
712             textMsg.iconSelfExplanatory = iconId.selfExplanatory;
713         }
714 
715         textMsg.responseNeeded = false;
716         mCmdParams = new DisplayTextParams(cmdDet, textMsg);
717 
718         if (iconId != null) {
719             mloadIcon = true;
720             mIconLoadState = LOAD_SINGLE_ICON;
721             mIconLoader.loadIcon(iconId.recordNumber, this
722                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
723             return true;
724         }
725         return false;
726     }
727 
728     /**
729      * Processes SET_UP_EVENT_LIST proactive command from the SIM card.
730      *
731      * @param cmdDet Command Details object retrieved.
732      * @param ctlvs List of ComprehensionTlv objects following Command Details
733      *        object and Device Identities object within the proactive command
734      * @return false. This function always returns false meaning that the command
735      *         processing is  not pending and additional asynchronous processing
736      *         is not required.
737      */
processSetUpEventList(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)738     private boolean processSetUpEventList(CommandDetails cmdDet,
739             List<ComprehensionTlv> ctlvs) {
740 
741         CatLog.d(this, "process SetUpEventList");
742         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.EVENT_LIST, ctlvs);
743         if (ctlv != null) {
744             try {
745                 byte[] rawValue = ctlv.getRawValue();
746                 int valueIndex = ctlv.getValueIndex();
747                 int valueLen = ctlv.getLength();
748                 int[] eventList = new int[valueLen];
749                 int eventValue = -1;
750                 int i = 0;
751                 while (valueLen > 0) {
752                     eventValue = rawValue[valueIndex] & 0xff;
753                     valueIndex++;
754                     valueLen--;
755 
756                     switch (eventValue) {
757                         case USER_ACTIVITY_EVENT:
758                         case IDLE_SCREEN_AVAILABLE_EVENT:
759                         case LANGUAGE_SELECTION_EVENT:
760                         case BROWSER_TERMINATION_EVENT:
761                         case BROWSING_STATUS_EVENT:
762                             eventList[i] = eventValue;
763                             i++;
764                             break;
765                         default:
766                             break;
767                     }
768 
769                 }
770                 mCmdParams = new SetEventListParams(cmdDet, eventList);
771             } catch (IndexOutOfBoundsException e) {
772                 CatLog.e(this, " IndexOutofBoundException in processSetUpEventList");
773             }
774         }
775         return false;
776     }
777 
778     /**
779      * Processes LAUNCH_BROWSER proactive command from the SIM card.
780      *
781      * @param cmdDet Command Details container object.
782      * @param ctlvs List of ComprehensionTlv objects following Command Details
783      *        object and Device Identities object within the proactive command
784      * @return true if the command is processing is pending and additional
785      *         asynchronous processing is required.
786      * @throws ResultException
787      */
processLaunchBrowser(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)788     private boolean processLaunchBrowser(CommandDetails cmdDet,
789             List<ComprehensionTlv> ctlvs) throws ResultException {
790 
791         CatLog.d(this, "process LaunchBrowser");
792 
793         TextMessage confirmMsg = new TextMessage();
794         IconId iconId = null;
795         String url = null;
796 
797         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.URL, ctlvs);
798         if (ctlv != null) {
799             try {
800                 byte[] rawValue = ctlv.getRawValue();
801                 int valueIndex = ctlv.getValueIndex();
802                 int valueLen = ctlv.getLength();
803                 if (valueLen > 0) {
804                     url = GsmAlphabet.gsm8BitUnpackedToString(rawValue,
805                             valueIndex, valueLen);
806                 } else {
807                     url = null;
808                 }
809             } catch (IndexOutOfBoundsException e) {
810                 throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
811             }
812         }
813 
814         // parse alpha identifier.
815         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
816         confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
817 
818         // parse icon identifier
819         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
820         if (ctlv != null) {
821             iconId = ValueParser.retrieveIconId(ctlv);
822             confirmMsg.iconSelfExplanatory = iconId.selfExplanatory;
823         }
824 
825         // parse command qualifier value.
826         LaunchBrowserMode mode;
827         switch (cmdDet.commandQualifier) {
828         case 0x00:
829         default:
830             mode = LaunchBrowserMode.LAUNCH_IF_NOT_ALREADY_LAUNCHED;
831             break;
832         case 0x02:
833             mode = LaunchBrowserMode.USE_EXISTING_BROWSER;
834             break;
835         case 0x03:
836             mode = LaunchBrowserMode.LAUNCH_NEW_BROWSER;
837             break;
838         }
839 
840         mCmdParams = new LaunchBrowserParams(cmdDet, confirmMsg, url, mode);
841 
842         if (iconId != null) {
843             mIconLoadState = LOAD_SINGLE_ICON;
844             mIconLoader.loadIcon(iconId.recordNumber, this
845                     .obtainMessage(MSG_ID_LOAD_ICON_DONE));
846             return true;
847         }
848         return false;
849     }
850 
851      /**
852      * Processes PLAY_TONE proactive command from the SIM card.
853      *
854      * @param cmdDet Command Details container object.
855      * @param ctlvs List of ComprehensionTlv objects following Command Details
856      *        object and Device Identities object within the proactive command
857      * @return true if the command is processing is pending and additional
858      *         asynchronous processing is required.t
859      * @throws ResultException
860      */
processPlayTone(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)861     private boolean processPlayTone(CommandDetails cmdDet,
862             List<ComprehensionTlv> ctlvs) throws ResultException {
863 
864         CatLog.d(this, "process PlayTone");
865 
866         Tone tone = null;
867         TextMessage textMsg = new TextMessage();
868         Duration duration = null;
869         IconId iconId = null;
870 
871         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TONE, ctlvs);
872         if (ctlv != null) {
873             // Nothing to do for null objects.
874             if (ctlv.getLength() > 0) {
875                 try {
876                     byte[] rawValue = ctlv.getRawValue();
877                     int valueIndex = ctlv.getValueIndex();
878                     int toneVal = rawValue[valueIndex];
879                     tone = Tone.fromInt(toneVal);
880                 } catch (IndexOutOfBoundsException e) {
881                     throw new ResultException(
882                             ResultCode.CMD_DATA_NOT_UNDERSTOOD);
883                 }
884             }
885         }
886         // parse alpha identifier
887         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
888         if (ctlv != null) {
889             textMsg.text = ValueParser.retrieveAlphaId(ctlv);
890             // Assign the tone message text to empty string, if alpha identifier
891             // data is null. If no alpha identifier tlv is present, then tone
892             // message text will be null.
893             if (textMsg.text == null) textMsg.text = "";
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      */
processSetupCall(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)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 
processProvideLocalInfo(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)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     /**
1004      * Processes LANGUAGE_NOTIFICATION proactive command from the SIM card.
1005      *
1006      * The SPECIFIC_LANGUAGE notification sets the specified language.
1007      * The NON_SPECIFIC_LANGUAGE notification restores the last specifically set language.
1008      *
1009      * @param cmdDet Command Details object retrieved from the proactive command object
1010      * @param ctlvs List of ComprehensionTlv objects following Command Details
1011      *        object and Device Identities object within the proactive command
1012      * @return false. This function always returns false meaning that the command
1013      *         processing is  not pending and additional asynchronous processing
1014      *         is not required.
1015      */
processLanguageNotification(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)1016     private boolean processLanguageNotification(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
1017             throws ResultException {
1018         CatLog.d(this, "process Language Notification");
1019 
1020         String desiredLanguage = null;
1021         String currentLanguage = Locale.getDefault().getLanguage();
1022         switch (cmdDet.commandQualifier) {
1023             case NON_SPECIFIC_LANGUAGE:
1024                 if (!TextUtils.isEmpty(mSavedLanguage) && (!TextUtils.isEmpty(mRequestedLanguage)
1025                         && mRequestedLanguage.equals(currentLanguage))) {
1026                     CatLog.d(this, "Non-specific language notification changes the language "
1027                             + "setting back to " + mSavedLanguage);
1028                     desiredLanguage = mSavedLanguage;
1029                 }
1030 
1031                 mSavedLanguage = null;
1032                 mRequestedLanguage = null;
1033                 break;
1034             case SPECIFIC_LANGUAGE:
1035                 ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.LANGUAGE, ctlvs);
1036                 if (ctlv != null) {
1037                     int valueLen = ctlv.getLength();
1038                     if (valueLen != 2) {
1039                         throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
1040                     }
1041 
1042                     byte[] rawValue = ctlv.getRawValue();
1043                     int valueIndex = ctlv.getValueIndex();
1044                     desiredLanguage = GsmAlphabet.gsm8BitUnpackedToString(rawValue, valueIndex, 2);
1045 
1046                     if (TextUtils.isEmpty(mSavedLanguage) || (!TextUtils.isEmpty(mRequestedLanguage)
1047                             && !mRequestedLanguage.equals(currentLanguage))) {
1048                         mSavedLanguage = currentLanguage;
1049                     }
1050                     mRequestedLanguage = desiredLanguage;
1051                     CatLog.d(this, "Specific language notification changes the language setting to "
1052                             + mRequestedLanguage);
1053                 }
1054                 break;
1055             default:
1056                 CatLog.d(this, "LN[" + cmdDet.commandQualifier + "] Command Not Supported");
1057                 break;
1058         }
1059 
1060         mCmdParams = new LanguageParams(cmdDet, desiredLanguage);
1061         return false;
1062     }
1063 
processBIPClient(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)1064     private boolean processBIPClient(CommandDetails cmdDet,
1065                                      List<ComprehensionTlv> ctlvs) throws ResultException {
1066         AppInterface.CommandType commandType =
1067                                     AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
1068         if (commandType != null) {
1069             CatLog.d(this, "process "+ commandType.name());
1070         }
1071 
1072         TextMessage textMsg = new TextMessage();
1073         IconId iconId = null;
1074         ComprehensionTlv ctlv = null;
1075         boolean has_alpha_id = false;
1076 
1077         // parse alpha identifier
1078         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
1079         if (ctlv != null) {
1080             textMsg.text = ValueParser.retrieveAlphaId(ctlv);
1081             CatLog.d(this, "alpha TLV text=" + textMsg.text);
1082             has_alpha_id = true;
1083         }
1084 
1085         // parse icon identifier
1086         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
1087         if (ctlv != null) {
1088             iconId = ValueParser.retrieveIconId(ctlv);
1089             textMsg.iconSelfExplanatory = iconId.selfExplanatory;
1090         }
1091 
1092         textMsg.responseNeeded = false;
1093         mCmdParams = new BIPClientParams(cmdDet, textMsg, has_alpha_id);
1094 
1095         if (iconId != null) {
1096             mIconLoadState = LOAD_SINGLE_ICON;
1097             mIconLoader.loadIcon(iconId.recordNumber, obtainMessage(MSG_ID_LOAD_ICON_DONE));
1098             return true;
1099         }
1100         return false;
1101     }
1102 
1103     @UnsupportedAppUsage
dispose()1104     public void dispose() {
1105         mIconLoader.dispose();
1106         mIconLoader = null;
1107         mCmdParams = null;
1108         mCaller = null;
1109         sInstance = null;
1110     }
1111 }
1112