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