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 android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ResolveInfo;
23 import android.content.res.Resources.NotFoundException;
24 import android.os.AsyncResult;
25 import android.os.Handler;
26 import android.os.HandlerThread;
27 import android.os.Message;
28 import android.os.SystemProperties;
29 import android.telephony.SubscriptionManager;
30 import android.telephony.TelephonyManager;
31 
32 import com.android.internal.telephony.CommandsInterface;
33 import com.android.internal.telephony.PhoneConstants;
34 import com.android.internal.telephony.SubscriptionController;
35 import com.android.internal.telephony.uicc.IccFileHandler;
36 import com.android.internal.telephony.uicc.IccRecords;
37 import com.android.internal.telephony.uicc.IccUtils;
38 import com.android.internal.telephony.uicc.UiccCard;
39 import com.android.internal.telephony.uicc.UiccCardApplication;
40 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
41 import com.android.internal.telephony.uicc.IccRefreshResponse;
42 import com.android.internal.telephony.uicc.UiccController;
43 
44 import java.io.ByteArrayOutputStream;
45 import java.util.List;
46 import java.util.Locale;
47 
48 import static com.android.internal.telephony.cat.CatCmdMessage.
49                    SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
50 import static com.android.internal.telephony.cat.CatCmdMessage.
51                    SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
52 
53 class RilMessage {
54     int mId;
55     Object mData;
56     ResultCode mResCode;
57 
RilMessage(int msgId, String rawData)58     RilMessage(int msgId, String rawData) {
59         mId = msgId;
60         mData = rawData;
61     }
62 
RilMessage(RilMessage other)63     RilMessage(RilMessage other) {
64         mId = other.mId;
65         mData = other.mData;
66         mResCode = other.mResCode;
67     }
68 }
69 
70 /**
71  * Class that implements SIM Toolkit Telephony Service. Interacts with the RIL
72  * and application.
73  *
74  * {@hide}
75  */
76 public class CatService extends Handler implements AppInterface {
77     private static final boolean DBG = false;
78 
79     // Class members
80     private static IccRecords mIccRecords;
81     private static UiccCardApplication mUiccApplication;
82 
83     // Service members.
84     // Protects singleton instance lazy initialization.
85     private static final Object sInstanceLock = new Object();
86     private static CatService[] sInstance = null;
87     private CommandsInterface mCmdIf;
88     private Context mContext;
89     private CatCmdMessage mCurrntCmd = null;
90     private CatCmdMessage mMenuCmd = null;
91 
92     private RilMessageDecoder mMsgDecoder = null;
93     private boolean mStkAppInstalled = false;
94 
95     private UiccController mUiccController;
96     private CardState mCardState = CardState.CARDSTATE_ABSENT;
97 
98     // Service constants.
99     protected static final int MSG_ID_SESSION_END              = 1;
100     protected static final int MSG_ID_PROACTIVE_COMMAND        = 2;
101     protected static final int MSG_ID_EVENT_NOTIFY             = 3;
102     protected static final int MSG_ID_CALL_SETUP               = 4;
103     static final int MSG_ID_REFRESH                  = 5;
104     static final int MSG_ID_RESPONSE                 = 6;
105     static final int MSG_ID_SIM_READY                = 7;
106 
107     protected static final int MSG_ID_ICC_CHANGED    = 8;
108     protected static final int MSG_ID_ALPHA_NOTIFY   = 9;
109 
110     static final int MSG_ID_RIL_MSG_DECODED          = 10;
111 
112     // Events to signal SIM presence or absent in the device.
113     private static final int MSG_ID_ICC_RECORDS_LOADED       = 20;
114 
115     //Events to signal SIM REFRESH notificatations
116     private static final int MSG_ID_ICC_REFRESH  = 30;
117 
118     private static final int DEV_ID_KEYPAD      = 0x01;
119     private static final int DEV_ID_DISPLAY     = 0x02;
120     private static final int DEV_ID_UICC        = 0x81;
121     private static final int DEV_ID_TERMINAL    = 0x82;
122     private static final int DEV_ID_NETWORK     = 0x83;
123 
124     static final String STK_DEFAULT = "Default Message";
125 
126     private HandlerThread mHandlerThread;
127     private int mSlotId;
128 
129     /* For multisim catservice should not be singleton */
CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, Context context, IccFileHandler fh, UiccCard ic, int slotId)130     private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir,
131             Context context, IccFileHandler fh, UiccCard ic, int slotId) {
132         if (ci == null || ca == null || ir == null || context == null || fh == null
133                 || ic == null) {
134             throw new NullPointerException(
135                     "Service: Input parameters must not be null");
136         }
137         mCmdIf = ci;
138         mContext = context;
139         mSlotId = slotId;
140         mHandlerThread = new HandlerThread("Cat Telephony service" + slotId);
141         mHandlerThread.start();
142 
143         // Get the RilMessagesDecoder for decoding the messages.
144         mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId);
145         if (null == mMsgDecoder) {
146             CatLog.d(this, "Null RilMessageDecoder instance");
147             return;
148         }
149         mMsgDecoder.start();
150 
151         // Register ril events handling.
152         mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
153         mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
154         mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
155         mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
156         //mCmdIf.setOnSimRefresh(this, MSG_ID_REFRESH, null);
157 
158         mCmdIf.registerForIccRefresh(this, MSG_ID_ICC_REFRESH, null);
159         mCmdIf.setOnCatCcAlphaNotify(this, MSG_ID_ALPHA_NOTIFY, null);
160 
161         mIccRecords = ir;
162         mUiccApplication = ca;
163 
164         // Register for SIM ready event.
165         mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
166         CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this);
167 
168 
169         mUiccController = UiccController.getInstance();
170         mUiccController.registerForIccChanged(this, MSG_ID_ICC_CHANGED, null);
171 
172         // Check if STK application is available
173         mStkAppInstalled = isStkAppInstalled();
174 
175         CatLog.d(this, "Running CAT service on Slotid: " + mSlotId +
176                 ". STK app installed:" + mStkAppInstalled);
177     }
178 
179     /**
180      * Used for instantiating the Service from the Card.
181      *
182      * @param ci CommandsInterface object
183      * @param context phone app context
184      * @param ic Icc card
185      * @param slotId to know the index of card
186      * @return The only Service object in the system
187      */
getInstance(CommandsInterface ci, Context context, UiccCard ic, int slotId)188     public static CatService getInstance(CommandsInterface ci,
189             Context context, UiccCard ic, int slotId) {
190         UiccCardApplication ca = null;
191         IccFileHandler fh = null;
192         IccRecords ir = null;
193         if (ic != null) {
194             /* Since Cat is not tied to any application, but rather is Uicc application
195              * in itself - just get first FileHandler and IccRecords object
196              */
197             ca = ic.getApplicationIndex(0);
198             if (ca != null) {
199                 fh = ca.getIccFileHandler();
200                 ir = ca.getIccRecords();
201             }
202         }
203 
204         synchronized (sInstanceLock) {
205             if (sInstance == null) {
206                 int simCount = TelephonyManager.getDefault().getSimCount();
207                 sInstance = new CatService[simCount];
208                 for (int i = 0; i < simCount; i++) {
209                     sInstance[i] = null;
210                 }
211             }
212             if (sInstance[slotId] == null) {
213                 if (ci == null || ca == null || ir == null || context == null || fh == null
214                         || ic == null) {
215                     return null;
216                 }
217 
218                 sInstance[slotId] = new CatService(ci, ca, ir, context, fh, ic, slotId);
219             } else if ((ir != null) && (mIccRecords != ir)) {
220                 if (mIccRecords != null) {
221                     mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]);
222                 }
223 
224                 mIccRecords = ir;
225                 mUiccApplication = ca;
226 
227                 mIccRecords.registerForRecordsLoaded(sInstance[slotId],
228                         MSG_ID_ICC_RECORDS_LOADED, null);
229                 CatLog.d(sInstance[slotId], "registerForRecordsLoaded slotid=" + slotId
230                         + " instance:" + sInstance[slotId]);
231             }
232             return sInstance[slotId];
233         }
234     }
235 
dispose()236     public void dispose() {
237         synchronized (sInstanceLock) {
238             CatLog.d(this, "Disposing CatService object");
239             mIccRecords.unregisterForRecordsLoaded(this);
240 
241             // Clean up stk icon if dispose is called
242             broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_ABSENT, null);
243 
244             mCmdIf.unSetOnCatSessionEnd(this);
245             mCmdIf.unSetOnCatProactiveCmd(this);
246             mCmdIf.unSetOnCatEvent(this);
247             mCmdIf.unSetOnCatCallSetUp(this);
248             mCmdIf.unSetOnCatCcAlphaNotify(this);
249 
250             mCmdIf.unregisterForIccRefresh(this);
251             if (mUiccController != null) {
252                 mUiccController.unregisterForIccChanged(this);
253                 mUiccController = null;
254             }
255             mMsgDecoder.dispose();
256             mMsgDecoder = null;
257             mHandlerThread.quit();
258             mHandlerThread = null;
259             removeCallbacksAndMessages(null);
260             if (sInstance != null) {
261                 if (SubscriptionManager.isValidSlotIndex(mSlotId)) {
262                     sInstance[mSlotId] = null;
263                 } else {
264                     CatLog.d(this, "error: invaild slot id: " + mSlotId);
265                 }
266             }
267         }
268     }
269 
270     @Override
finalize()271     protected void finalize() {
272         CatLog.d(this, "Service finalized");
273     }
274 
handleRilMsg(RilMessage rilMsg)275     private void handleRilMsg(RilMessage rilMsg) {
276         if (rilMsg == null) {
277             return;
278         }
279 
280         // dispatch messages
281         CommandParams cmdParams = null;
282         switch (rilMsg.mId) {
283         case MSG_ID_EVENT_NOTIFY:
284             if (rilMsg.mResCode == ResultCode.OK) {
285                 cmdParams = (CommandParams) rilMsg.mData;
286                 if (cmdParams != null) {
287                     handleCommand(cmdParams, false);
288                 }
289             }
290             break;
291         case MSG_ID_PROACTIVE_COMMAND:
292             try {
293                 cmdParams = (CommandParams) rilMsg.mData;
294             } catch (ClassCastException e) {
295                 // for error handling : cast exception
296                 CatLog.d(this, "Fail to parse proactive command");
297                 // Don't send Terminal Resp if command detail is not available
298                 if (mCurrntCmd != null) {
299                     sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD,
300                                      false, 0x00, null);
301                 }
302                 break;
303             }
304             if (cmdParams != null) {
305                 if (rilMsg.mResCode == ResultCode.OK) {
306                     handleCommand(cmdParams, true);
307                 } else {
308                     // for proactive commands that couldn't be decoded
309                     // successfully respond with the code generated by the
310                     // message decoder.
311                     sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode,
312                             false, 0, null);
313                 }
314             }
315             break;
316         case MSG_ID_REFRESH:
317             cmdParams = (CommandParams) rilMsg.mData;
318             if (cmdParams != null) {
319                 handleCommand(cmdParams, false);
320             }
321             break;
322         case MSG_ID_SESSION_END:
323             handleSessionEnd();
324             break;
325         case MSG_ID_CALL_SETUP:
326             // prior event notify command supplied all the information
327             // needed for set up call processing.
328             break;
329         }
330     }
331 
332     /**
333      * This function validates the events in SETUP_EVENT_LIST which are currently
334      * supported by the Android framework. In case of SETUP_EVENT_LIST has NULL events
335      * or no events, all the events need to be reset.
336      */
isSupportedSetupEventCommand(CatCmdMessage cmdMsg)337     private boolean isSupportedSetupEventCommand(CatCmdMessage cmdMsg) {
338         boolean flag = true;
339 
340         for (int eventVal: cmdMsg.getSetEventList().eventList) {
341             CatLog.d(this,"Event: " + eventVal);
342             switch (eventVal) {
343                 /* Currently android is supporting only the below events in SetupEventList
344                  * Language Selection.  */
345                 case IDLE_SCREEN_AVAILABLE_EVENT:
346                 case LANGUAGE_SELECTION_EVENT:
347                     break;
348                 default:
349                     flag = false;
350             }
351         }
352         return flag;
353     }
354 
355     /**
356      * Handles RIL_UNSOL_STK_EVENT_NOTIFY or RIL_UNSOL_STK_PROACTIVE_COMMAND command
357      * from RIL.
358      * Sends valid proactive command data to the application using intents.
359      * RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE will be send back if the command is
360      * from RIL_UNSOL_STK_PROACTIVE_COMMAND.
361      */
handleCommand(CommandParams cmdParams, boolean isProactiveCmd)362     private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) {
363         CatLog.d(this, cmdParams.getCommandType().name());
364 
365         // Log all proactive commands.
366         if (isProactiveCmd) {
367             if (mUiccController != null) {
368                 mUiccController.addCardLog("ProactiveCommand mSlotId=" + mSlotId +
369                         " cmdParams=" + cmdParams);
370             }
371         }
372 
373         CharSequence message;
374         ResultCode resultCode;
375         CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
376         switch (cmdParams.getCommandType()) {
377             case SET_UP_MENU:
378                 if (removeMenu(cmdMsg.getMenu())) {
379                     mMenuCmd = null;
380                 } else {
381                     mMenuCmd = cmdMsg;
382                 }
383                 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED
384                                                                             : ResultCode.OK;
385                 sendTerminalResponse(cmdParams.mCmdDet, resultCode, false, 0, null);
386                 break;
387             case DISPLAY_TEXT:
388                 break;
389             case REFRESH:
390                 // ME side only handles refresh commands which meant to remove IDLE
391                 // MODE TEXT.
392                 cmdParams.mCmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value();
393                 break;
394             case SET_UP_IDLE_MODE_TEXT:
395                 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED
396                                                                             : ResultCode.OK;
397                 sendTerminalResponse(cmdParams.mCmdDet,resultCode, false, 0, null);
398                 break;
399             case SET_UP_EVENT_LIST:
400                 if (isSupportedSetupEventCommand(cmdMsg)) {
401                     sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
402                 } else {
403                     sendTerminalResponse(cmdParams.mCmdDet, ResultCode.BEYOND_TERMINAL_CAPABILITY,
404                             false, 0, null);
405                 }
406                 break;
407             case PROVIDE_LOCAL_INFORMATION:
408                 ResponseData resp;
409                 switch (cmdParams.mCmdDet.commandQualifier) {
410                     case CommandParamsFactory.DTTZ_SETTING:
411                         resp = new DTTZResponseData(null);
412                         sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp);
413                         break;
414                     case CommandParamsFactory.LANGUAGE_SETTING:
415                         resp = new LanguageResponseData(Locale.getDefault().getLanguage());
416                         sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, resp);
417                         break;
418                     default:
419                         sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
420                 }
421                 // No need to start STK app here.
422                 return;
423             case LAUNCH_BROWSER:
424                 if ((((LaunchBrowserParams) cmdParams).mConfirmMsg.text != null)
425                         && (((LaunchBrowserParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) {
426                     message = mContext.getText(com.android.internal.R.string.launchBrowserDefault);
427                     ((LaunchBrowserParams) cmdParams).mConfirmMsg.text = message.toString();
428                 }
429                 break;
430             case SELECT_ITEM:
431             case GET_INPUT:
432             case GET_INKEY:
433                 break;
434             case SEND_DTMF:
435             case SEND_SMS:
436             case SEND_SS:
437             case SEND_USSD:
438                 if ((((DisplayTextParams)cmdParams).mTextMsg.text != null)
439                         && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) {
440                     message = mContext.getText(com.android.internal.R.string.sending);
441                     ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString();
442                 }
443                 break;
444             case PLAY_TONE:
445                 break;
446             case SET_UP_CALL:
447                 if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null)
448                         && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) {
449                     message = mContext.getText(com.android.internal.R.string.SetupCallDefault);
450                     ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
451                 }
452                 break;
453             case OPEN_CHANNEL:
454             case CLOSE_CHANNEL:
455             case RECEIVE_DATA:
456             case SEND_DATA:
457                 BIPClientParams cmd = (BIPClientParams) cmdParams;
458                 /* Per 3GPP specification 102.223,
459                  * if the alpha identifier is not provided by the UICC,
460                  * the terminal MAY give information to the user
461                  * noAlphaUsrCnf defines if you need to show user confirmation or not
462                  */
463                 boolean noAlphaUsrCnf = false;
464                 try {
465                     noAlphaUsrCnf = mContext.getResources().getBoolean(
466                             com.android.internal.R.bool.config_stkNoAlphaUsrCnf);
467                 } catch (NotFoundException e) {
468                     noAlphaUsrCnf = false;
469                 }
470                 if ((cmd.mTextMsg.text == null) && (cmd.mHasAlphaId || noAlphaUsrCnf)) {
471                     CatLog.d(this, "cmd " + cmdParams.getCommandType() + " with null alpha id");
472                     // If alpha length is zero, we just respond with OK.
473                     if (isProactiveCmd) {
474                         sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
475                     } else if (cmdParams.getCommandType() == CommandType.OPEN_CHANNEL) {
476                         mCmdIf.handleCallSetupRequestFromSim(true, null);
477                     }
478                     return;
479                 }
480                 // Respond with permanent failure to avoid retry if STK app is not present.
481                 if (!mStkAppInstalled) {
482                     CatLog.d(this, "No STK application found.");
483                     if (isProactiveCmd) {
484                         sendTerminalResponse(cmdParams.mCmdDet,
485                                              ResultCode.BEYOND_TERMINAL_CAPABILITY,
486                                              false, 0, null);
487                         return;
488                     }
489                 }
490                 /*
491                  * CLOSE_CHANNEL, RECEIVE_DATA and SEND_DATA can be delivered by
492                  * either PROACTIVE_COMMAND or EVENT_NOTIFY.
493                  * If PROACTIVE_COMMAND is used for those commands, send terminal
494                  * response here.
495                  */
496                 if (isProactiveCmd &&
497                     ((cmdParams.getCommandType() == CommandType.CLOSE_CHANNEL) ||
498                      (cmdParams.getCommandType() == CommandType.RECEIVE_DATA) ||
499                      (cmdParams.getCommandType() == CommandType.SEND_DATA))) {
500                     sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
501                 }
502                 break;
503             default:
504                 CatLog.d(this, "Unsupported command");
505                 return;
506         }
507         mCurrntCmd = cmdMsg;
508         broadcastCatCmdIntent(cmdMsg);
509     }
510 
511 
broadcastCatCmdIntent(CatCmdMessage cmdMsg)512     private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) {
513         Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
514         intent.putExtra("STK CMD", cmdMsg);
515         intent.putExtra("SLOT_ID", mSlotId);
516         intent.setComponent(AppInterface.getDefaultSTKApplication());
517         CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId);
518         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
519     }
520 
521     /**
522      * Handles RIL_UNSOL_STK_SESSION_END unsolicited command from RIL.
523      *
524      */
handleSessionEnd()525     private void handleSessionEnd() {
526         CatLog.d(this, "SESSION END on "+ mSlotId);
527 
528         mCurrntCmd = mMenuCmd;
529         Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
530         intent.putExtra("SLOT_ID", mSlotId);
531         intent.setComponent(AppInterface.getDefaultSTKApplication());
532         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
533     }
534 
535 
sendTerminalResponse(CommandDetails cmdDet, ResultCode resultCode, boolean includeAdditionalInfo, int additionalInfo, ResponseData resp)536     private void sendTerminalResponse(CommandDetails cmdDet,
537             ResultCode resultCode, boolean includeAdditionalInfo,
538             int additionalInfo, ResponseData resp) {
539 
540         if (cmdDet == null) {
541             return;
542         }
543         ByteArrayOutputStream buf = new ByteArrayOutputStream();
544 
545         Input cmdInput = null;
546         if (mCurrntCmd != null) {
547             cmdInput = mCurrntCmd.geInput();
548         }
549 
550         // command details
551         int tag = ComprehensionTlvTag.COMMAND_DETAILS.value();
552         if (cmdDet.compRequired) {
553             tag |= 0x80;
554         }
555         buf.write(tag);
556         buf.write(0x03); // length
557         buf.write(cmdDet.commandNumber);
558         buf.write(cmdDet.typeOfCommand);
559         buf.write(cmdDet.commandQualifier);
560 
561         // device identities
562         // According to TS102.223/TS31.111 section 6.8 Structure of
563         // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
564         // the ME should set the CR(comprehension required) flag to
565         // comprehension not required.(CR=0)"
566         // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
567         // the CR flag is not set.
568         tag = ComprehensionTlvTag.DEVICE_IDENTITIES.value();
569         buf.write(tag);
570         buf.write(0x02); // length
571         buf.write(DEV_ID_TERMINAL); // source device id
572         buf.write(DEV_ID_UICC); // destination device id
573 
574         // result
575         tag = ComprehensionTlvTag.RESULT.value();
576         if (cmdDet.compRequired) {
577             tag |= 0x80;
578         }
579         buf.write(tag);
580         int length = includeAdditionalInfo ? 2 : 1;
581         buf.write(length);
582         buf.write(resultCode.value());
583 
584         // additional info
585         if (includeAdditionalInfo) {
586             buf.write(additionalInfo);
587         }
588 
589         // Fill optional data for each corresponding command
590         if (resp != null) {
591             resp.format(buf);
592         } else {
593             encodeOptionalTags(cmdDet, resultCode, cmdInput, buf);
594         }
595 
596         byte[] rawData = buf.toByteArray();
597         String hexString = IccUtils.bytesToHexString(rawData);
598         if (DBG) {
599             CatLog.d(this, "TERMINAL RESPONSE: " + hexString);
600         }
601 
602         mCmdIf.sendTerminalResponse(hexString, null);
603     }
604 
encodeOptionalTags(CommandDetails cmdDet, ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf)605     private void encodeOptionalTags(CommandDetails cmdDet,
606             ResultCode resultCode, Input cmdInput, ByteArrayOutputStream buf) {
607         CommandType cmdType = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
608         if (cmdType != null) {
609             switch (cmdType) {
610                 case GET_INKEY:
611                     // ETSI TS 102 384,27.22.4.2.8.4.2.
612                     // If it is a response for GET_INKEY command and the response timeout
613                     // occured, then add DURATION TLV for variable timeout case.
614                     if ((resultCode.value() == ResultCode.NO_RESPONSE_FROM_USER.value()) &&
615                         (cmdInput != null) && (cmdInput.duration != null)) {
616                         getInKeyResponse(buf, cmdInput);
617                     }
618                     break;
619                 case PROVIDE_LOCAL_INFORMATION:
620                     if ((cmdDet.commandQualifier == CommandParamsFactory.LANGUAGE_SETTING) &&
621                         (resultCode.value() == ResultCode.OK.value())) {
622                         getPliResponse(buf);
623                     }
624                     break;
625                 default:
626                     CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet);
627                     break;
628             }
629         } else {
630             CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet);
631         }
632     }
633 
getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput)634     private void getInKeyResponse(ByteArrayOutputStream buf, Input cmdInput) {
635         int tag = ComprehensionTlvTag.DURATION.value();
636 
637         buf.write(tag);
638         buf.write(0x02); // length
639         buf.write(cmdInput.duration.timeUnit.SECOND.value()); // Time (Unit,Seconds)
640         buf.write(cmdInput.duration.timeInterval); // Time Duration
641     }
642 
getPliResponse(ByteArrayOutputStream buf)643     private void getPliResponse(ByteArrayOutputStream buf) {
644         // Locale Language Setting
645         final String lang = Locale.getDefault().getLanguage();
646 
647         if (lang != null) {
648             // tag
649             int tag = ComprehensionTlvTag.LANGUAGE.value();
650             buf.write(tag);
651             ResponseData.writeLength(buf, lang.length());
652             buf.write(lang.getBytes(), 0, lang.length());
653         }
654     }
655 
sendMenuSelection(int menuId, boolean helpRequired)656     private void sendMenuSelection(int menuId, boolean helpRequired) {
657 
658         ByteArrayOutputStream buf = new ByteArrayOutputStream();
659 
660         // tag
661         int tag = BerTlv.BER_MENU_SELECTION_TAG;
662         buf.write(tag);
663 
664         // length
665         buf.write(0x00); // place holder
666 
667         // device identities
668         tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
669         buf.write(tag);
670         buf.write(0x02); // length
671         buf.write(DEV_ID_KEYPAD); // source device id
672         buf.write(DEV_ID_UICC); // destination device id
673 
674         // item identifier
675         tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
676         buf.write(tag);
677         buf.write(0x01); // length
678         buf.write(menuId); // menu identifier chosen
679 
680         // help request
681         if (helpRequired) {
682             tag = ComprehensionTlvTag.HELP_REQUEST.value();
683             buf.write(tag);
684             buf.write(0x00); // length
685         }
686 
687         byte[] rawData = buf.toByteArray();
688 
689         // write real length
690         int len = rawData.length - 2; // minus (tag + length)
691         rawData[1] = (byte) len;
692 
693         String hexString = IccUtils.bytesToHexString(rawData);
694 
695         mCmdIf.sendEnvelope(hexString, null);
696     }
697 
eventDownload(int event, int sourceId, int destinationId, byte[] additionalInfo, boolean oneShot)698     private void eventDownload(int event, int sourceId, int destinationId,
699             byte[] additionalInfo, boolean oneShot) {
700 
701         ByteArrayOutputStream buf = new ByteArrayOutputStream();
702 
703         // tag
704         int tag = BerTlv.BER_EVENT_DOWNLOAD_TAG;
705         buf.write(tag);
706 
707         // length
708         buf.write(0x00); // place holder, assume length < 128.
709 
710         // event list
711         tag = 0x80 | ComprehensionTlvTag.EVENT_LIST.value();
712         buf.write(tag);
713         buf.write(0x01); // length
714         buf.write(event); // event value
715 
716         // device identities
717         tag = 0x80 | ComprehensionTlvTag.DEVICE_IDENTITIES.value();
718         buf.write(tag);
719         buf.write(0x02); // length
720         buf.write(sourceId); // source device id
721         buf.write(destinationId); // destination device id
722 
723         /*
724          * Check for type of event download to be sent to UICC - Browser
725          * termination,Idle screen available, User activity, Language selection
726          * etc as mentioned under ETSI TS 102 223 section 7.5
727          */
728 
729         /*
730          * Currently the below events are supported:
731          * Language Selection Event.
732          * Other event download commands should be encoded similar way
733          */
734         /* TODO: eventDownload should be extended for other Envelope Commands */
735         switch (event) {
736             case IDLE_SCREEN_AVAILABLE_EVENT:
737                 CatLog.d(sInstance, " Sending Idle Screen Available event download to ICC");
738                 break;
739             case LANGUAGE_SELECTION_EVENT:
740                 CatLog.d(sInstance, " Sending Language Selection event download to ICC");
741                 tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
742                 buf.write(tag);
743                 // Language length should be 2 byte
744                 buf.write(0x02);
745                 break;
746             default:
747                 break;
748         }
749 
750         // additional information
751         if (additionalInfo != null) {
752             for (byte b : additionalInfo) {
753                 buf.write(b);
754             }
755         }
756 
757         byte[] rawData = buf.toByteArray();
758 
759         // write real length
760         int len = rawData.length - 2; // minus (tag + length)
761         rawData[1] = (byte) len;
762 
763         String hexString = IccUtils.bytesToHexString(rawData);
764 
765         CatLog.d(this, "ENVELOPE COMMAND: " + hexString);
766 
767         mCmdIf.sendEnvelope(hexString, null);
768     }
769 
770     /**
771      * Used by application to get an AppInterface object.
772      *
773      * @return The only Service object in the system
774      */
775     //TODO Need to take care for MSIM
getInstance()776     public static AppInterface getInstance() {
777         int slotId = PhoneConstants.DEFAULT_CARD_INDEX;
778         SubscriptionController sControl = SubscriptionController.getInstance();
779         if (sControl != null) {
780             slotId = sControl.getSlotIndex(sControl.getDefaultSubId());
781         }
782         return getInstance(null, null, null, slotId);
783     }
784 
785     /**
786      * Used by application to get an AppInterface object.
787      *
788      * @return The only Service object in the system
789      */
getInstance(int slotId)790     public static AppInterface getInstance(int slotId) {
791         return getInstance(null, null, null, slotId);
792     }
793 
794     @Override
handleMessage(Message msg)795     public void handleMessage(Message msg) {
796         CatLog.d(this, "handleMessage[" + msg.what + "]");
797 
798         switch (msg.what) {
799         case MSG_ID_SESSION_END:
800         case MSG_ID_PROACTIVE_COMMAND:
801         case MSG_ID_EVENT_NOTIFY:
802         case MSG_ID_REFRESH:
803             CatLog.d(this, "ril message arrived,slotid:" + mSlotId);
804             String data = null;
805             if (msg.obj != null) {
806                 AsyncResult ar = (AsyncResult) msg.obj;
807                 if (ar != null && ar.result != null) {
808                     try {
809                         data = (String) ar.result;
810                     } catch (ClassCastException e) {
811                         break;
812                     }
813                 }
814             }
815             mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
816             break;
817         case MSG_ID_CALL_SETUP:
818             mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
819             break;
820         case MSG_ID_ICC_RECORDS_LOADED:
821             break;
822         case MSG_ID_RIL_MSG_DECODED:
823             handleRilMsg((RilMessage) msg.obj);
824             break;
825         case MSG_ID_RESPONSE:
826             handleCmdResponse((CatResponseMessage) msg.obj);
827             break;
828         case MSG_ID_ICC_CHANGED:
829             CatLog.d(this, "MSG_ID_ICC_CHANGED");
830             updateIccAvailability();
831             break;
832         case MSG_ID_ICC_REFRESH:
833             if (msg.obj != null) {
834                 AsyncResult ar = (AsyncResult) msg.obj;
835                 if (ar != null && ar.result != null) {
836                     broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_PRESENT,
837                                   (IccRefreshResponse) ar.result);
838                 } else {
839                     CatLog.d(this,"Icc REFRESH with exception: " + ar.exception);
840                 }
841             } else {
842                 CatLog.d(this, "IccRefresh Message is null");
843             }
844             break;
845         case MSG_ID_ALPHA_NOTIFY:
846             CatLog.d(this, "Received CAT CC Alpha message from card");
847             if (msg.obj != null) {
848                 AsyncResult ar = (AsyncResult) msg.obj;
849                 if (ar != null && ar.result != null) {
850                     broadcastAlphaMessage((String)ar.result);
851                 } else {
852                     CatLog.d(this, "CAT Alpha message: ar.result is null");
853                 }
854             } else {
855                 CatLog.d(this, "CAT Alpha message: msg.obj is null");
856             }
857             break;
858         default:
859             throw new AssertionError("Unrecognized CAT command: " + msg.what);
860         }
861     }
862 
863     /**
864      ** This function sends a CARD status (ABSENT, PRESENT, REFRESH) to STK_APP.
865      ** This is triggered during ICC_REFRESH or CARD STATE changes. In case
866      ** REFRESH, additional information is sent in 'refresh_result'
867      **
868      **/
broadcastCardStateAndIccRefreshResp(CardState cardState, IccRefreshResponse iccRefreshState)869     private void  broadcastCardStateAndIccRefreshResp(CardState cardState,
870             IccRefreshResponse iccRefreshState) {
871         Intent intent = new Intent(AppInterface.CAT_ICC_STATUS_CHANGE);
872         boolean cardPresent = (cardState == CardState.CARDSTATE_PRESENT);
873 
874         if (iccRefreshState != null) {
875             //This case is when MSG_ID_ICC_REFRESH is received.
876             intent.putExtra(AppInterface.REFRESH_RESULT, iccRefreshState.refreshResult);
877             CatLog.d(this, "Sending IccResult with Result: "
878                     + iccRefreshState.refreshResult);
879         }
880 
881         // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true).
882         intent.putExtra(AppInterface.CARD_STATUS, cardPresent);
883         intent.setComponent(AppInterface.getDefaultSTKApplication());
884         CatLog.d(this, "Sending Card Status: "
885                 + cardState + " " + "cardPresent: " + cardPresent);
886         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
887     }
888 
broadcastAlphaMessage(String alphaString)889     private void broadcastAlphaMessage(String alphaString) {
890         CatLog.d(this, "Broadcasting CAT Alpha message from card: " + alphaString);
891         Intent intent = new Intent(AppInterface.CAT_ALPHA_NOTIFY_ACTION);
892         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
893         intent.putExtra(AppInterface.ALPHA_STRING, alphaString);
894         intent.putExtra("SLOT_ID", mSlotId);
895         intent.setComponent(AppInterface.getDefaultSTKApplication());
896         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
897     }
898 
899     @Override
onCmdResponse(CatResponseMessage resMsg)900     public synchronized void onCmdResponse(CatResponseMessage resMsg) {
901         if (resMsg == null) {
902             return;
903         }
904         // queue a response message.
905         Message msg = obtainMessage(MSG_ID_RESPONSE, resMsg);
906         msg.sendToTarget();
907     }
908 
validateResponse(CatResponseMessage resMsg)909     private boolean validateResponse(CatResponseMessage resMsg) {
910         boolean validResponse = false;
911         if ((resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_EVENT_LIST.value())
912                 || (resMsg.mCmdDet.typeOfCommand == CommandType.SET_UP_MENU.value())) {
913             CatLog.d(this, "CmdType: " + resMsg.mCmdDet.typeOfCommand);
914             validResponse = true;
915         } else if (mCurrntCmd != null) {
916             validResponse = resMsg.mCmdDet.compareTo(mCurrntCmd.mCmdDet);
917             CatLog.d(this, "isResponse for last valid cmd: " + validResponse);
918         }
919         return validResponse;
920     }
921 
removeMenu(Menu menu)922     private boolean removeMenu(Menu menu) {
923         try {
924             if (menu.items.size() == 1 && menu.items.get(0) == null) {
925                 return true;
926             }
927         } catch (NullPointerException e) {
928             CatLog.d(this, "Unable to get Menu's items size");
929             return true;
930         }
931         return false;
932     }
933 
handleCmdResponse(CatResponseMessage resMsg)934     private void handleCmdResponse(CatResponseMessage resMsg) {
935         // Make sure the response details match the last valid command. An invalid
936         // response is a one that doesn't have a corresponding proactive command
937         // and sending it can "confuse" the baseband/ril.
938         // One reason for out of order responses can be UI glitches. For example,
939         // if the application launch an activity, and that activity is stored
940         // by the framework inside the history stack. That activity will be
941         // available for relaunch using the latest application dialog
942         // (long press on the home button). Relaunching that activity can send
943         // the same command's result again to the CatService and can cause it to
944         // get out of sync with the SIM. This can happen in case of
945         // non-interactive type Setup Event List and SETUP_MENU proactive commands.
946         // Stk framework would have already sent Terminal Response to Setup Event
947         // List and SETUP_MENU proactive commands. After sometime Stk app will send
948         // Envelope Command/Event Download. In which case, the response details doesn't
949         // match with last valid command (which are not related).
950         // However, we should allow Stk framework to send the message to ICC.
951         if (!validateResponse(resMsg)) {
952             return;
953         }
954         ResponseData resp = null;
955         boolean helpRequired = false;
956         CommandDetails cmdDet = resMsg.getCmdDetails();
957         AppInterface.CommandType type = AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
958 
959         switch (resMsg.mResCode) {
960         case HELP_INFO_REQUIRED:
961             helpRequired = true;
962             // fall through
963         case OK:
964         case PRFRMD_WITH_PARTIAL_COMPREHENSION:
965         case PRFRMD_WITH_MISSING_INFO:
966         case PRFRMD_WITH_ADDITIONAL_EFS_READ:
967         case PRFRMD_ICON_NOT_DISPLAYED:
968         case PRFRMD_MODIFIED_BY_NAA:
969         case PRFRMD_LIMITED_SERVICE:
970         case PRFRMD_WITH_MODIFICATION:
971         case PRFRMD_NAA_NOT_ACTIVE:
972         case PRFRMD_TONE_NOT_PLAYED:
973         case LAUNCH_BROWSER_ERROR:
974         case TERMINAL_CRNTLY_UNABLE_TO_PROCESS:
975             switch (type) {
976             case SET_UP_MENU:
977                 helpRequired = resMsg.mResCode == ResultCode.HELP_INFO_REQUIRED;
978                 sendMenuSelection(resMsg.mUsersMenuSelection, helpRequired);
979                 return;
980             case SELECT_ITEM:
981                 resp = new SelectItemResponseData(resMsg.mUsersMenuSelection);
982                 break;
983             case GET_INPUT:
984             case GET_INKEY:
985                 Input input = mCurrntCmd.geInput();
986                 if (!input.yesNo) {
987                     // when help is requested there is no need to send the text
988                     // string object.
989                     if (!helpRequired) {
990                         resp = new GetInkeyInputResponseData(resMsg.mUsersInput,
991                                 input.ucs2, input.packed);
992                     }
993                 } else {
994                     resp = new GetInkeyInputResponseData(
995                             resMsg.mUsersYesNoSelection);
996                 }
997                 break;
998             case DISPLAY_TEXT:
999                 if (resMsg.mResCode == ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS) {
1000                     // For screenbusy case there will be addtional information in the terminal
1001                     // response. And the value of the additional information byte is 0x01.
1002                     resMsg.setAdditionalInfo(0x01);
1003                 } else {
1004                     resMsg.mIncludeAdditionalInfo = false;
1005                     resMsg.mAdditionalInfo = 0;
1006                 }
1007                 break;
1008             case LAUNCH_BROWSER:
1009                 break;
1010             // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR
1011             case OPEN_CHANNEL:
1012             case SET_UP_CALL:
1013                 mCmdIf.handleCallSetupRequestFromSim(resMsg.mUsersConfirm, null);
1014                 // No need to send terminal response for SET UP CALL. The user's
1015                 // confirmation result is send back using a dedicated ril message
1016                 // invoked by the CommandInterface call above.
1017                 mCurrntCmd = null;
1018                 return;
1019             case SET_UP_EVENT_LIST:
1020                 if (IDLE_SCREEN_AVAILABLE_EVENT == resMsg.mEventValue) {
1021                     eventDownload(resMsg.mEventValue, DEV_ID_DISPLAY, DEV_ID_UICC,
1022                             resMsg.mAddedInfo, false);
1023                  } else {
1024                      eventDownload(resMsg.mEventValue, DEV_ID_TERMINAL, DEV_ID_UICC,
1025                             resMsg.mAddedInfo, false);
1026                  }
1027                 // No need to send the terminal response after event download.
1028                 return;
1029             default:
1030                 break;
1031             }
1032             break;
1033         case BACKWARD_MOVE_BY_USER:
1034         case USER_NOT_ACCEPT:
1035             // if the user dismissed the alert dialog for a
1036             // setup call/open channel, consider that as the user
1037             // rejecting the call. Use dedicated API for this, rather than
1038             // sending a terminal response.
1039             if (type == CommandType.SET_UP_CALL || type == CommandType.OPEN_CHANNEL) {
1040                 mCmdIf.handleCallSetupRequestFromSim(false, null);
1041                 mCurrntCmd = null;
1042                 return;
1043             } else {
1044                 resp = null;
1045             }
1046             break;
1047         case NO_RESPONSE_FROM_USER:
1048         case UICC_SESSION_TERM_BY_USER:
1049             resp = null;
1050             break;
1051         default:
1052             return;
1053         }
1054         sendTerminalResponse(cmdDet, resMsg.mResCode, resMsg.mIncludeAdditionalInfo,
1055                 resMsg.mAdditionalInfo, resp);
1056         mCurrntCmd = null;
1057     }
1058 
isStkAppInstalled()1059     private boolean isStkAppInstalled() {
1060         Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
1061         PackageManager pm = mContext.getPackageManager();
1062         List<ResolveInfo> broadcastReceivers =
1063                             pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA);
1064         int numReceiver = broadcastReceivers == null ? 0 : broadcastReceivers.size();
1065 
1066         return (numReceiver > 0);
1067     }
1068 
update(CommandsInterface ci, Context context, UiccCard ic)1069     public void update(CommandsInterface ci,
1070             Context context, UiccCard ic) {
1071         UiccCardApplication ca = null;
1072         IccRecords ir = null;
1073 
1074         if (ic != null) {
1075             /* Since Cat is not tied to any application, but rather is Uicc application
1076              * in itself - just get first FileHandler and IccRecords object
1077              */
1078             ca = ic.getApplicationIndex(0);
1079             if (ca != null) {
1080                 ir = ca.getIccRecords();
1081             }
1082         }
1083 
1084         synchronized (sInstanceLock) {
1085             if ((ir != null) && (mIccRecords != ir)) {
1086                 if (mIccRecords != null) {
1087                     mIccRecords.unregisterForRecordsLoaded(this);
1088                 }
1089 
1090                 CatLog.d(this,
1091                         "Reinitialize the Service with SIMRecords and UiccCardApplication");
1092                 mIccRecords = ir;
1093                 mUiccApplication = ca;
1094 
1095                 // re-Register for SIM ready event.
1096                 mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
1097                 CatLog.d(this, "registerForRecordsLoaded slotid=" + mSlotId + " instance:" + this);
1098             }
1099         }
1100     }
1101 
updateIccAvailability()1102     void updateIccAvailability() {
1103         if (null == mUiccController) {
1104             return;
1105         }
1106 
1107         CardState newState = CardState.CARDSTATE_ABSENT;
1108         UiccCard newCard = mUiccController.getUiccCard(mSlotId);
1109         if (newCard != null) {
1110             newState = newCard.getCardState();
1111         }
1112         CardState oldState = mCardState;
1113         mCardState = newState;
1114         CatLog.d(this,"New Card State = " + newState + " " + "Old Card State = " + oldState);
1115         if (oldState == CardState.CARDSTATE_PRESENT &&
1116                 newState != CardState.CARDSTATE_PRESENT) {
1117             broadcastCardStateAndIccRefreshResp(newState, null);
1118         } else if (oldState != CardState.CARDSTATE_PRESENT &&
1119                 newState == CardState.CARDSTATE_PRESENT) {
1120             // Card moved to PRESENT STATE.
1121             mCmdIf.reportStkServiceIsRunning(null);
1122         }
1123     }
1124 }
1125