• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.stk;
18 
19 import android.app.ActivityManager;
20 import android.app.ActivityManager.RunningTaskInfo;
21 import android.app.AlertDialog;
22 import android.app.KeyguardManager;
23 import android.app.Notification;
24 import android.app.NotificationChannel;
25 import android.app.NotificationManager;
26 import android.app.PendingIntent;
27 import android.app.Service;
28 import android.app.Activity;
29 import android.app.ActivityManagerNative;
30 import android.app.IProcessObserver;
31 import android.content.BroadcastReceiver;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.DialogInterface;
35 import android.content.Intent;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Configuration;
39 import android.content.res.Resources;
40 import android.content.res.Resources.NotFoundException;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.media.RingtoneManager;
44 import android.net.Uri;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.os.Looper;
49 import android.os.Message;
50 import android.os.Parcel;
51 import android.os.PersistableBundle;
52 import android.os.PowerManager;
53 import android.os.RemoteException;
54 import android.os.ServiceManager;
55 import android.os.SystemProperties;
56 import android.os.Vibrator;
57 import android.provider.Settings;
58 import android.support.v4.content.LocalBroadcastManager;
59 import android.telephony.CarrierConfigManager;
60 import android.telephony.SubscriptionInfo;
61 import android.telephony.SubscriptionManager;
62 import android.telephony.TelephonyManager;
63 import android.text.TextUtils;
64 import android.view.Gravity;
65 import android.view.IWindowManager;
66 import android.view.LayoutInflater;
67 import android.view.View;
68 import android.view.WindowManager;
69 import android.view.WindowManagerPolicyConstants;
70 import android.widget.ImageView;
71 import android.widget.TextView;
72 import android.widget.Toast;
73 import android.content.IntentFilter;
74 
75 import com.android.internal.telephony.cat.AppInterface;
76 import com.android.internal.telephony.cat.Input;
77 import com.android.internal.telephony.cat.LaunchBrowserMode;
78 import com.android.internal.telephony.cat.Menu;
79 import com.android.internal.telephony.cat.Item;
80 import com.android.internal.telephony.cat.ResultCode;
81 import com.android.internal.telephony.cat.CatCmdMessage;
82 import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings;
83 import com.android.internal.telephony.cat.CatCmdMessage.SetupEventListSettings;
84 import com.android.internal.telephony.cat.CatLog;
85 import com.android.internal.telephony.cat.CatResponseMessage;
86 import com.android.internal.telephony.cat.TextMessage;
87 import com.android.internal.telephony.cat.ToneSettings;
88 import com.android.internal.telephony.uicc.IccRefreshResponse;
89 import com.android.internal.telephony.PhoneConstants;
90 import com.android.internal.telephony.GsmAlphabet;
91 import com.android.internal.telephony.cat.CatService;
92 
93 import java.util.Iterator;
94 import java.util.LinkedList;
95 import java.util.List;
96 import java.util.regex.Pattern;
97 
98 import static com.android.internal.telephony.cat.CatCmdMessage.
99                    SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
100 import static com.android.internal.telephony.cat.CatCmdMessage.
101                    SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
102 import static com.android.internal.telephony.cat.CatCmdMessage.
103                    SetupEventListConstants.USER_ACTIVITY_EVENT;
104 
105 /**
106  * SIM toolkit application level service. Interacts with Telephopny messages,
107  * application's launch and user input from STK UI elements.
108  *
109  */
110 public class StkAppService extends Service implements Runnable {
111 
112     // members
113     protected class StkContext {
114         protected CatCmdMessage mMainCmd = null;
115         protected CatCmdMessage mCurrentCmd = null;
116         protected CatCmdMessage mCurrentMenuCmd = null;
117         protected Menu mCurrentMenu = null;
118         protected String lastSelectedItem = null;
119         protected boolean mMenuIsVisible = false;
120         protected boolean mIsInputPending = false;
121         protected boolean mIsMenuPending = false;
122         protected boolean mIsDialogPending = false;
123         protected boolean mNotificationOnKeyguard = false;
124         protected boolean mNoResponseFromUser = false;
125         protected boolean launchBrowser = false;
126         protected BrowserSettings mBrowserSettings = null;
127         protected LinkedList<DelayedCmd> mCmdsQ = null;
128         protected boolean mCmdInProgress = false;
129         protected int mStkServiceState = STATE_UNKNOWN;
130         protected int mSetupMenuState = STATE_NOT_EXIST;
131         protected int mMenuState = StkMenuActivity.STATE_INIT;
132         protected int mOpCode = -1;
133         private Activity mActivityInstance = null;
134         private Activity mDialogInstance = null;
135         private Activity mImmediateDialogInstance = null;
136         private int mSlotId = 0;
137         private SetupEventListSettings mSetupEventListSettings = null;
138         private boolean mClearSelectItem = false;
139         private boolean mDisplayTextDlgIsVisibile = false;
140         private CatCmdMessage mCurrentSetupEventCmd = null;
141         private CatCmdMessage mIdleModeTextCmd = null;
142         private boolean mIdleModeTextVisible = false;
143         // Determins whether the current session was initiated by user operation.
144         protected boolean mIsSessionFromUser = false;
setPendingActivityInstance(Activity act)145         final synchronized void setPendingActivityInstance(Activity act) {
146             CatLog.d(this, "setPendingActivityInstance act : " + mSlotId + ", " + act);
147             callSetActivityInstMsg(OP_SET_ACT_INST, mSlotId, act);
148         }
getPendingActivityInstance()149         final synchronized Activity getPendingActivityInstance() {
150             CatLog.d(this, "getPendingActivityInstance act : " + mSlotId + ", " +
151                     mActivityInstance);
152             return mActivityInstance;
153         }
setPendingDialogInstance(Activity act)154         final synchronized void setPendingDialogInstance(Activity act) {
155             CatLog.d(this, "setPendingDialogInstance act : " + mSlotId + ", " + act);
156             callSetActivityInstMsg(OP_SET_DAL_INST, mSlotId, act);
157         }
getPendingDialogInstance()158         final synchronized Activity getPendingDialogInstance() {
159             CatLog.d(this, "getPendingDialogInstance act : " + mSlotId + ", " +
160                     mDialogInstance);
161             return mDialogInstance;
162         }
setImmediateDialogInstance(Activity act)163         final synchronized void setImmediateDialogInstance(Activity act) {
164             CatLog.d(this, "setImmediateDialogInstance act : " + mSlotId + ", " + act);
165             callSetActivityInstMsg(OP_SET_IMMED_DAL_INST, mSlotId, act);
166         }
getImmediateDialogInstance()167         final synchronized Activity getImmediateDialogInstance() {
168             CatLog.d(this, "getImmediateDialogInstance act : " + mSlotId + ", " +
169                     mImmediateDialogInstance);
170             return mImmediateDialogInstance;
171         }
172     }
173 
174     private volatile Looper mServiceLooper;
175     private volatile ServiceHandler mServiceHandler;
176     private Context mContext = null;
177     private NotificationManager mNotificationManager = null;
178     static StkAppService sInstance = null;
179     private AppInterface[] mStkService = null;
180     private StkContext[] mStkContext = null;
181     private int mSimCount = 0;
182     private IProcessObserver.Stub mProcessObserver = null;
183     private BroadcastReceiver mLocaleChangeReceiver = null;
184     private TonePlayer mTonePlayer = null;
185     private Vibrator mVibrator = null;
186     private BroadcastReceiver mUserActivityReceiver = null;
187 
188     // Used for setting FLAG_ACTIVITY_NO_USER_ACTION when
189     // creating an intent.
190     private enum InitiatedByUserAction {
191         yes,            // The action was started via a user initiated action
192         unknown,        // Not known for sure if user initated the action
193     }
194 
195     // constants
196     static final String OPCODE = "op";
197     static final String CMD_MSG = "cmd message";
198     static final String RES_ID = "response id";
199     static final String MENU_SELECTION = "menu selection";
200     static final String INPUT = "input";
201     static final String HELP = "help";
202     static final String CONFIRMATION = "confirm";
203     static final String CHOICE = "choice";
204     static final String SLOT_ID = "SLOT_ID";
205     static final String STK_CMD = "STK CMD";
206     static final String STK_DIALOG_URI = "stk://com.android.stk/dialog/";
207     static final String STK_MENU_URI = "stk://com.android.stk/menu/";
208     static final String STK_INPUT_URI = "stk://com.android.stk/input/";
209     static final String STK_TONE_URI = "stk://com.android.stk/tone/";
210     static final String FINISH_TONE_ACTIVITY_ACTION =
211                                 "android.intent.action.stk.finish_activity";
212 
213     // These below constants are used for SETUP_EVENT_LIST
214     static final String SETUP_EVENT_TYPE = "event";
215     static final String SETUP_EVENT_CAUSE = "cause";
216 
217     // operations ids for different service functionality.
218     static final int OP_CMD = 1;
219     static final int OP_RESPONSE = 2;
220     static final int OP_LAUNCH_APP = 3;
221     static final int OP_END_SESSION = 4;
222     static final int OP_BOOT_COMPLETED = 5;
223     private static final int OP_DELAYED_MSG = 6;
224     static final int OP_CARD_STATUS_CHANGED = 7;
225     static final int OP_SET_ACT_INST = 8;
226     static final int OP_SET_DAL_INST = 9;
227     static final int OP_LOCALE_CHANGED = 10;
228     static final int OP_ALPHA_NOTIFY = 11;
229     static final int OP_IDLE_SCREEN = 12;
230     static final int OP_SET_IMMED_DAL_INST = 13;
231 
232     //Invalid SetupEvent
233     static final int INVALID_SETUP_EVENT = 0xFF;
234 
235     // Message id to signal stop tone due to play tone timeout.
236     private static final int OP_STOP_TONE = 16;
237 
238     // Message id to signal stop tone on user keyback.
239     static final int OP_STOP_TONE_USER = 17;
240 
241     // Message id to remove stop tone message from queue.
242     private static final int STOP_TONE_WHAT = 100;
243 
244     // Message id to send user activity event to card.
245     private static final int OP_USER_ACTIVITY = 20;
246 
247     // Response ids
248     static final int RES_ID_MENU_SELECTION = 11;
249     static final int RES_ID_INPUT = 12;
250     static final int RES_ID_CONFIRM = 13;
251     static final int RES_ID_DONE = 14;
252     static final int RES_ID_CHOICE = 15;
253 
254     static final int RES_ID_TIMEOUT = 20;
255     static final int RES_ID_BACKWARD = 21;
256     static final int RES_ID_END_SESSION = 22;
257     static final int RES_ID_EXIT = 23;
258     static final int RES_ID_ERROR = 24;
259 
260     static final int YES = 1;
261     static final int NO = 0;
262 
263     static final int STATE_UNKNOWN = -1;
264     static final int STATE_NOT_EXIST = 0;
265     static final int STATE_EXIST = 1;
266 
267     private static final String PACKAGE_NAME = "com.android.stk";
268     private static final String STK_MENU_ACTIVITY_NAME = PACKAGE_NAME + ".StkMenuActivity";
269     private static final String STK_INPUT_ACTIVITY_NAME = PACKAGE_NAME + ".StkInputActivity";
270     private static final String STK_DIALOG_ACTIVITY_NAME = PACKAGE_NAME + ".StkDialogActivity";
271     // Notification id used to display Idle Mode text in NotificationManager.
272     private static final int STK_NOTIFICATION_ID = 333;
273     // Notification channel containing all mobile service messages notifications.
274     private static final String STK_NOTIFICATION_CHANNEL_ID = "mobileServiceMessages";
275 
276     private static final String LOG_TAG = new Object(){}.getClass().getEnclosingClass().getName();
277 
278     static final String SESSION_ENDED = "session_ended";
279 
280     // Inner class used for queuing telephony messages (proactive commands,
281     // session end) while the service is busy processing a previous message.
282     private class DelayedCmd {
283         // members
284         int id;
285         CatCmdMessage msg;
286         int slotId;
287 
DelayedCmd(int id, CatCmdMessage msg, int slotId)288         DelayedCmd(int id, CatCmdMessage msg, int slotId) {
289             this.id = id;
290             this.msg = msg;
291             this.slotId = slotId;
292         }
293     }
294 
295     // system property to set the STK specific default url for launch browser proactive cmds
296     private static final String STK_BROWSER_DEFAULT_URL_SYSPROP = "persist.radio.stk.default_url";
297 
298     private static final int NOTIFICATION_ON_KEYGUARD = 1;
299     private static final long[] VIBRATION_PATTERN = new long[] { 0, 350, 250, 350 };
300     private BroadcastReceiver mUserPresentReceiver = null;
301 
302     @Override
onCreate()303     public void onCreate() {
304         CatLog.d(LOG_TAG, "onCreate()+");
305         // Initialize members
306         int i = 0;
307         mContext = getBaseContext();
308         mSimCount = TelephonyManager.from(mContext).getSimCount();
309         CatLog.d(LOG_TAG, "simCount: " + mSimCount);
310         mStkService = new AppInterface[mSimCount];
311         mStkContext = new StkContext[mSimCount];
312 
313         for (i = 0; i < mSimCount; i++) {
314             CatLog.d(LOG_TAG, "slotId: " + i);
315             mStkService[i] = CatService.getInstance(i);
316             mStkContext[i] = new StkContext();
317             mStkContext[i].mSlotId = i;
318             mStkContext[i].mCmdsQ = new LinkedList<DelayedCmd>();
319         }
320 
321         Thread serviceThread = new Thread(null, this, "Stk App Service");
322         serviceThread.start();
323         mNotificationManager = (NotificationManager) mContext
324                 .getSystemService(Context.NOTIFICATION_SERVICE);
325         sInstance = this;
326     }
327 
328     @Override
onStart(Intent intent, int startId)329     public void onStart(Intent intent, int startId) {
330         if (intent == null) {
331             CatLog.d(LOG_TAG, "StkAppService onStart intent is null so return");
332             return;
333         }
334 
335         Bundle args = intent.getExtras();
336         if (args == null) {
337             CatLog.d(LOG_TAG, "StkAppService onStart args is null so return");
338             return;
339         }
340 
341         int op = args.getInt(OPCODE);
342         int slotId = 0;
343         int i = 0;
344         if (op != OP_BOOT_COMPLETED) {
345             slotId = args.getInt(SLOT_ID);
346         }
347         CatLog.d(LOG_TAG, "onStart sim id: " + slotId + ", op: " + op + ", *****");
348         if ((slotId >= 0 && slotId < mSimCount) && mStkService[slotId] == null) {
349             mStkService[slotId] = CatService.getInstance(slotId);
350             if (mStkService[slotId] == null) {
351                 CatLog.d(LOG_TAG, "mStkService is: " + mStkContext[slotId].mStkServiceState);
352                 mStkContext[slotId].mStkServiceState = STATE_NOT_EXIST;
353                 //Check other StkService state.
354                 //If all StkServices are not available, stop itself and uninstall apk.
355                 for (i = PhoneConstants.SIM_ID_1; i < mSimCount; i++) {
356                     if (i != slotId
357                             && (mStkService[i] != null)
358                             && (mStkContext[i].mStkServiceState == STATE_UNKNOWN
359                             || mStkContext[i].mStkServiceState == STATE_EXIST)) {
360                        break;
361                    }
362                 }
363             } else {
364                 mStkContext[slotId].mStkServiceState = STATE_EXIST;
365             }
366             if (i == mSimCount) {
367                 stopSelf();
368                 StkAppInstaller.unInstall(mContext);
369                 return;
370             }
371         }
372 
373         waitForLooper();
374 
375         Message msg = mServiceHandler.obtainMessage();
376         msg.arg1 = op;
377         msg.arg2 = slotId;
378         switch(msg.arg1) {
379         case OP_CMD:
380             msg.obj = args.getParcelable(CMD_MSG);
381             break;
382         case OP_RESPONSE:
383         case OP_CARD_STATUS_CHANGED:
384         case OP_LOCALE_CHANGED:
385         case OP_ALPHA_NOTIFY:
386         case OP_IDLE_SCREEN:
387             msg.obj = args;
388             /* falls through */
389         case OP_LAUNCH_APP:
390         case OP_END_SESSION:
391         case OP_BOOT_COMPLETED:
392             break;
393         case OP_STOP_TONE_USER:
394             msg.obj = args;
395             msg.what = STOP_TONE_WHAT;
396             break;
397         default:
398             return;
399         }
400         mServiceHandler.sendMessage(msg);
401     }
402 
403     @Override
onDestroy()404     public void onDestroy() {
405         CatLog.d(LOG_TAG, "onDestroy()");
406         unregisterUserActivityReceiver();
407         unregisterProcessObserver();
408         unregisterLocaleChangeReceiver();
409         sInstance = null;
410         waitForLooper();
411         mServiceLooper.quit();
412     }
413 
414     @Override
onBind(Intent intent)415     public IBinder onBind(Intent intent) {
416         return null;
417     }
418 
run()419     public void run() {
420         Looper.prepare();
421 
422         mServiceLooper = Looper.myLooper();
423         mServiceHandler = new ServiceHandler();
424 
425         Looper.loop();
426     }
427 
428     /*
429      * Package api used by StkMenuActivity to indicate if its on the foreground.
430      */
indicateMenuVisibility(boolean visibility, int slotId)431     void indicateMenuVisibility(boolean visibility, int slotId) {
432         if (slotId >= 0 && slotId < mSimCount) {
433             mStkContext[slotId].mMenuIsVisible = visibility;
434         }
435     }
436 
437     /*
438      * Package api used by StkDialogActivity to indicate if its on the foreground.
439      */
setDisplayTextDlgVisibility(boolean visibility, int slotId)440     void setDisplayTextDlgVisibility(boolean visibility, int slotId) {
441         if (slotId >= 0 && slotId < mSimCount) {
442             mStkContext[slotId].mDisplayTextDlgIsVisibile = visibility;
443         }
444     }
445 
isInputPending(int slotId)446     boolean isInputPending(int slotId) {
447         if (slotId >= 0 && slotId < mSimCount) {
448             CatLog.d(LOG_TAG, "isInputFinishBySrv: " + mStkContext[slotId].mIsInputPending);
449             return mStkContext[slotId].mIsInputPending;
450         }
451         return false;
452     }
453 
isMenuPending(int slotId)454     boolean isMenuPending(int slotId) {
455         if (slotId >= 0 && slotId < mSimCount) {
456             CatLog.d(LOG_TAG, "isMenuPending: " + mStkContext[slotId].mIsMenuPending);
457             return mStkContext[slotId].mIsMenuPending;
458         }
459         return false;
460     }
461 
isDialogPending(int slotId)462     boolean isDialogPending(int slotId) {
463         if (slotId >= 0 && slotId < mSimCount) {
464             CatLog.d(LOG_TAG, "isDialogPending: " + mStkContext[slotId].mIsDialogPending);
465             return mStkContext[slotId].mIsDialogPending;
466         }
467         return false;
468     }
469 
isMainMenuAvailable(int slotId)470     boolean isMainMenuAvailable(int slotId) {
471         if (slotId >= 0 && slotId < mSimCount) {
472             // The main menu can handle the next user operation if the previous session finished.
473             return (mStkContext[slotId].lastSelectedItem == null) ? true : false;
474         }
475         return false;
476     }
477 
478     /*
479      * Package api used by StkMenuActivity to get its Menu parameter.
480      */
getMenu(int slotId)481     Menu getMenu(int slotId) {
482         CatLog.d(LOG_TAG, "StkAppService, getMenu, sim id: " + slotId);
483         if (slotId >=0 && slotId < mSimCount) {
484             return mStkContext[slotId].mCurrentMenu;
485         } else {
486             return null;
487         }
488     }
489 
490     /*
491      * Package api used by StkMenuActivity to get its Main Menu parameter.
492      */
getMainMenu(int slotId)493     Menu getMainMenu(int slotId) {
494         CatLog.d(LOG_TAG, "StkAppService, getMainMenu, sim id: " + slotId);
495         if (slotId >=0 && slotId < mSimCount && (mStkContext[slotId].mMainCmd != null)) {
496             Menu menu = mStkContext[slotId].mMainCmd.getMenu();
497             if (menu != null && mSimCount > PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
498                 // If alpha identifier or icon identifier with the self-explanatory qualifier is
499                 // specified in SET-UP MENU command, it should be more prioritized than preset ones.
500                 if (menu.title == null
501                         && (menu.titleIcon == null || !menu.titleIconSelfExplanatory)) {
502                     StkMenuConfig config = StkMenuConfig.getInstance(getApplicationContext());
503                     String label = config.getLabel(slotId);
504                     Bitmap icon = config.getIcon(slotId);
505                     if (label != null || icon != null) {
506                         Parcel parcel = Parcel.obtain();
507                         menu.writeToParcel(parcel, 0);
508                         parcel.setDataPosition(0);
509                         menu = Menu.CREATOR.createFromParcel(parcel);
510                         parcel.recycle();
511                         menu.title = label;
512                         menu.titleIcon = icon;
513                         menu.titleIconSelfExplanatory = false;
514                     }
515                 }
516             }
517             return menu;
518         } else {
519             return null;
520         }
521     }
522 
523     /*
524      * Package api used by UI Activities and Dialogs to communicate directly
525      * with the service to deliver state information and parameters.
526      */
getInstance()527     static StkAppService getInstance() {
528         return sInstance;
529     }
530 
waitForLooper()531     private void waitForLooper() {
532         while (mServiceHandler == null) {
533             synchronized (this) {
534                 try {
535                     wait(100);
536                 } catch (InterruptedException e) {
537                 }
538             }
539         }
540     }
541 
542     private final class ServiceHandler extends Handler {
543         @Override
handleMessage(Message msg)544         public void handleMessage(Message msg) {
545             if(null == msg) {
546                 CatLog.d(LOG_TAG, "ServiceHandler handleMessage msg is null");
547                 return;
548             }
549             int opcode = msg.arg1;
550             int slotId = msg.arg2;
551 
552             CatLog.d(LOG_TAG, "handleMessage opcode[" + opcode + "], sim id[" + slotId + "]");
553             if (opcode == OP_CMD && msg.obj != null &&
554                     ((CatCmdMessage)msg.obj).getCmdType()!= null) {
555                 CatLog.d(LOG_TAG, "cmdName[" + ((CatCmdMessage)msg.obj).getCmdType().name() + "]");
556             }
557             mStkContext[slotId].mOpCode = opcode;
558             switch (opcode) {
559             case OP_LAUNCH_APP:
560                 if (mStkContext[slotId].mMainCmd == null) {
561                     CatLog.d(LOG_TAG, "mMainCmd is null");
562                     // nothing todo when no SET UP MENU command didn't arrive.
563                     return;
564                 }
565                 CatLog.d(LOG_TAG, "handleMessage OP_LAUNCH_APP - mCmdInProgress[" +
566                         mStkContext[slotId].mCmdInProgress + "]");
567 
568                 //If there is a pending activity for the slot id,
569                 //just finish it and create a new one to handle the pending command.
570                 cleanUpInstanceStackBySlot(slotId);
571 
572                 CatLog.d(LOG_TAG, "Current cmd type: " +
573                         mStkContext[slotId].mCurrentCmd.getCmdType());
574                 //Restore the last command from stack by slot id.
575                 restoreInstanceFromStackBySlot(slotId);
576                 break;
577             case OP_CMD:
578                 CatLog.d(LOG_TAG, "[OP_CMD]");
579                 CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj;
580                 // There are two types of commands:
581                 // 1. Interactive - user's response is required.
582                 // 2. Informative - display a message, no interaction with the user.
583                 //
584                 // Informative commands can be handled immediately without any delay.
585                 // Interactive commands can't override each other. So if a command
586                 // is already in progress, we need to queue the next command until
587                 // the user has responded or a timeout expired.
588                 if (!isCmdInteractive(cmdMsg)) {
589                     handleCmd(cmdMsg, slotId);
590                 } else {
591                     if (!mStkContext[slotId].mCmdInProgress) {
592                         mStkContext[slotId].mCmdInProgress = true;
593                         handleCmd((CatCmdMessage) msg.obj, slotId);
594                     } else {
595                         CatLog.d(LOG_TAG, "[Interactive][in progress]");
596                         mStkContext[slotId].mCmdsQ.addLast(new DelayedCmd(OP_CMD,
597                                 (CatCmdMessage) msg.obj, slotId));
598                     }
599                 }
600                 break;
601             case OP_RESPONSE:
602                 handleCmdResponse((Bundle) msg.obj, slotId);
603                 // call delayed commands if needed.
604                 if (mStkContext[slotId].mCmdsQ.size() != 0) {
605                     callDelayedMsg(slotId);
606                 } else {
607                     mStkContext[slotId].mCmdInProgress = false;
608                 }
609                 break;
610             case OP_END_SESSION:
611                 if (!mStkContext[slotId].mCmdInProgress) {
612                     mStkContext[slotId].mCmdInProgress = true;
613                     handleSessionEnd(slotId);
614                 } else {
615                     mStkContext[slotId].mCmdsQ.addLast(
616                             new DelayedCmd(OP_END_SESSION, null, slotId));
617                 }
618                 break;
619             case OP_BOOT_COMPLETED:
620                 CatLog.d(LOG_TAG, " OP_BOOT_COMPLETED");
621                 int i = 0;
622                 for (i = PhoneConstants.SIM_ID_1; i < mSimCount; i++) {
623                     if (mStkContext[i].mMainCmd != null) {
624                         break;
625                     }
626                 }
627                 if (i == mSimCount) {
628                     StkAppInstaller.unInstall(mContext);
629                 }
630                 break;
631             case OP_DELAYED_MSG:
632                 handleDelayedCmd(slotId);
633                 break;
634             case OP_CARD_STATUS_CHANGED:
635                 CatLog.d(LOG_TAG, "Card/Icc Status change received");
636                 handleCardStatusChangeAndIccRefresh((Bundle) msg.obj, slotId);
637                 break;
638             case OP_SET_ACT_INST:
639                 Activity act = (Activity) msg.obj;
640                 if (mStkContext[slotId].mActivityInstance != act) {
641                     CatLog.d(LOG_TAG, "Set pending activity instance - " + act);
642                     Activity previous = mStkContext[slotId].mActivityInstance;
643                     mStkContext[slotId].mActivityInstance = act;
644                     // Finish the previous one if it was replaced with new one
645                     // but it has not been finished yet somehow.
646                     if (act != null && previous != null && !previous.isDestroyed()
647                             && !previous.isFinishing()) {
648                         CatLog.d(LOG_TAG, "Finish the previous pending activity - " + previous);
649                         previous.finish();
650                     }
651                     // Pending activity is registered in the following 2 scnarios;
652                     // A. TERMINAL RESPONSE was sent to the card.
653                     // B. Activity was moved to the background before TR is sent to the card.
654                     // No need to observe idle screen for the pending activity in the scenario A.
655                     if (act != null && mStkContext[slotId].mCmdInProgress) {
656                         startToObserveIdleScreen(slotId);
657                     } else {
658                         if (mStkContext[slotId].mCurrentCmd != null) {
659                             unregisterProcessObserver(
660                                     mStkContext[slotId].mCurrentCmd.getCmdType(), slotId);
661                         }
662                     }
663                 }
664                 break;
665             case OP_SET_DAL_INST:
666                 Activity dal = (Activity) msg.obj;
667                 if (mStkContext[slotId].mDialogInstance != dal) {
668                     CatLog.d(LOG_TAG, "Set pending dialog instance - " + dal);
669                     mStkContext[slotId].mDialogInstance = dal;
670                     if (dal != null) {
671                         startToObserveIdleScreen(slotId);
672                     } else {
673                         if (mStkContext[slotId].mCurrentCmd != null) {
674                             unregisterProcessObserver(
675                                     mStkContext[slotId].mCurrentCmd.getCmdType(), slotId);
676                         }
677                     }
678                 }
679                 break;
680             case OP_SET_IMMED_DAL_INST:
681                 Activity immedDal = (Activity) msg.obj;
682                 CatLog.d(LOG_TAG, "Set dialog instance for immediate response. " + immedDal);
683                 mStkContext[slotId].mImmediateDialogInstance = immedDal;
684                 break;
685             case OP_LOCALE_CHANGED:
686                 CatLog.d(this, "Locale Changed");
687                 for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
688                     checkForSetupEvent(LANGUAGE_SELECTION_EVENT, (Bundle) msg.obj, slot);
689                 }
690                 // rename all registered notification channels on locale change
691                 createAllChannels();
692                 break;
693             case OP_ALPHA_NOTIFY:
694                 handleAlphaNotify((Bundle) msg.obj);
695                 break;
696             case OP_IDLE_SCREEN:
697                for (int slot = 0; slot < mSimCount; slot++) {
698                     if (mStkContext[slot] != null) {
699                         handleIdleScreen(slot);
700                     }
701                 }
702                 break;
703             case OP_STOP_TONE_USER:
704             case OP_STOP_TONE:
705                 CatLog.d(this, "Stop tone");
706                 handleStopTone(msg, slotId);
707                 break;
708             case OP_USER_ACTIVITY:
709                 for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
710                     checkForSetupEvent(USER_ACTIVITY_EVENT, null, slot);
711                 }
712                 break;
713             }
714         }
715 
handleCardStatusChangeAndIccRefresh(Bundle args, int slotId)716         private void handleCardStatusChangeAndIccRefresh(Bundle args, int slotId) {
717             boolean cardStatus = args.getBoolean(AppInterface.CARD_STATUS);
718 
719             CatLog.d(LOG_TAG, "CardStatus: " + cardStatus);
720             if (cardStatus == false) {
721                 CatLog.d(LOG_TAG, "CARD is ABSENT");
722                 // Uninstall STKAPP, Clear Idle text, Stop StkAppService
723                 cancelIdleText(slotId);
724                 mStkContext[slotId].mCurrentMenu = null;
725                 mStkContext[slotId].mMainCmd = null;
726                 mStkService[slotId] = null;
727                 if (isAllOtherCardsAbsent(slotId)) {
728                     CatLog.d(LOG_TAG, "All CARDs are ABSENT");
729                     StkAppInstaller.unInstall(mContext);
730                     stopSelf();
731                 }
732             } else {
733                 IccRefreshResponse state = new IccRefreshResponse();
734                 state.refreshResult = args.getInt(AppInterface.REFRESH_RESULT);
735 
736                 CatLog.d(LOG_TAG, "Icc Refresh Result: "+ state.refreshResult);
737                 if ((state.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) ||
738                     (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET)) {
739                     // Clear Idle Text
740                     cancelIdleText(slotId);
741                 }
742             }
743         }
744     }
745     /*
746      * Check if all SIMs are absent except the id of slot equals "slotId".
747      */
isAllOtherCardsAbsent(int slotId)748     private boolean isAllOtherCardsAbsent(int slotId) {
749         TelephonyManager mTm = (TelephonyManager) mContext.getSystemService(
750                 Context.TELEPHONY_SERVICE);
751         int i = 0;
752 
753         for (i = 0; i < mSimCount; i++) {
754             if (i != slotId && mTm.hasIccCard(i)) {
755                 break;
756             }
757         }
758         if (i == mSimCount) {
759             return true;
760         } else {
761             return false;
762         }
763     }
764 
isScreenIdle()765     /* package */ boolean isScreenIdle() {
766         ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
767         List<RunningTaskInfo> tasks = am.getRunningTasks(1);
768         if (tasks == null || tasks.isEmpty()) {
769             return false;
770         }
771 
772         String top = tasks.get(0).topActivity.getPackageName();
773         if (top == null) {
774             return false;
775         }
776 
777         // We can assume that the screen is idle if the home application is in the foreground.
778         final Intent intent = new Intent(Intent.ACTION_MAIN, null);
779         intent.addCategory(Intent.CATEGORY_HOME);
780 
781         ResolveInfo info = getPackageManager().resolveActivity(intent,
782                 PackageManager.MATCH_DEFAULT_ONLY);
783         if (info != null) {
784             if (top.equals(info.activityInfo.packageName)) {
785                 return true;
786             }
787         }
788 
789         return false;
790     }
791 
startToObserveIdleScreen(int slotId)792     private void startToObserveIdleScreen(int slotId) {
793         if (!mStkContext[slotId].mIsSessionFromUser) {
794             if (!isScreenIdle()) {
795                 synchronized (this) {
796                     if (mProcessObserver == null && !mServiceHandler.hasMessages(OP_IDLE_SCREEN)) {
797                         registerProcessObserver();
798                     }
799                 }
800             } else {
801                 handleIdleScreen(slotId);
802             }
803         }
804     }
805 
handleIdleScreen(int slotId)806     private void handleIdleScreen(int slotId) {
807         // It might be hard for user to recognize that the dialog or screens belong to SIM Toolkit
808         // application if the current session was not initiated by user but by the SIM card,
809         // so it is recommended to send TERMINAL RESPONSE if user goes to the idle screen.
810         if (!mStkContext[slotId].mIsSessionFromUser) {
811             Activity dialog = mStkContext[slotId].getPendingDialogInstance();
812             if (dialog != null) {
813                 dialog.finish();
814                 mStkContext[slotId].mDialogInstance = null;
815             }
816             Activity activity = mStkContext[slotId].getPendingActivityInstance();
817             if (activity != null) {
818                 activity.finish();
819                 mStkContext[slotId].mActivityInstance = null;
820             }
821         }
822         // If the idle screen event is present in the list need to send the
823         // response to SIM.
824         CatLog.d(this, "Need to send IDLE SCREEN Available event to SIM");
825         checkForSetupEvent(IDLE_SCREEN_AVAILABLE_EVENT, null, slotId);
826 
827         if (mStkContext[slotId].mIdleModeTextCmd != null
828                 && !mStkContext[slotId].mIdleModeTextVisible) {
829             launchIdleText(slotId);
830         }
831     }
832 
sendScreenBusyResponse(int slotId)833     private void sendScreenBusyResponse(int slotId) {
834         if (mStkContext[slotId].mCurrentCmd == null) {
835             return;
836         }
837         CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentCmd);
838         CatLog.d(this, "SCREEN_BUSY");
839         resMsg.setResultCode(ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS);
840         mStkService[slotId].onCmdResponse(resMsg);
841         if (mStkContext[slotId].mCmdsQ.size() != 0) {
842             callDelayedMsg(slotId);
843         } else {
844             mStkContext[slotId].mCmdInProgress = false;
845         }
846     }
847 
848     /**
849      * Sends TERMINAL RESPONSE or ENVELOPE
850      *
851      * @param args detailed parameters of the response
852      * @param slotId slot identifier
853      */
sendResponse(Bundle args, int slotId)854     public void sendResponse(Bundle args, int slotId) {
855         Message msg = mServiceHandler.obtainMessage();
856         msg.arg1 = OP_RESPONSE;
857         msg.arg2 = slotId;
858         msg.obj = args;
859         mServiceHandler.sendMessage(msg);
860     }
861 
sendResponse(int resId, int slotId, boolean confirm)862     private void sendResponse(int resId, int slotId, boolean confirm) {
863         Message msg = mServiceHandler.obtainMessage();
864         msg.arg1 = OP_RESPONSE;
865         msg.arg2 = slotId;
866         Bundle args = new Bundle();
867         args.putInt(StkAppService.RES_ID, resId);
868         args.putBoolean(StkAppService.CONFIRMATION, confirm);
869         msg.obj = args;
870         mServiceHandler.sendMessage(msg);
871     }
872 
isCmdInteractive(CatCmdMessage cmd)873     private boolean isCmdInteractive(CatCmdMessage cmd) {
874         switch (cmd.getCmdType()) {
875         case SEND_DTMF:
876         case SEND_SMS:
877         case REFRESH:
878         case RUN_AT:
879         case SEND_SS:
880         case SEND_USSD:
881         case SET_UP_IDLE_MODE_TEXT:
882         case SET_UP_MENU:
883         case CLOSE_CHANNEL:
884         case RECEIVE_DATA:
885         case SEND_DATA:
886         case SET_UP_EVENT_LIST:
887             return false;
888         }
889 
890         return true;
891     }
892 
handleDelayedCmd(int slotId)893     private void handleDelayedCmd(int slotId) {
894         CatLog.d(LOG_TAG, "handleDelayedCmd, slotId: " + slotId);
895         if (mStkContext[slotId].mCmdsQ.size() != 0) {
896             DelayedCmd cmd = mStkContext[slotId].mCmdsQ.poll();
897             if (cmd != null) {
898                 CatLog.d(LOG_TAG, "handleDelayedCmd - queue size: " +
899                         mStkContext[slotId].mCmdsQ.size() +
900                         " id: " + cmd.id + "sim id: " + cmd.slotId);
901                 switch (cmd.id) {
902                 case OP_CMD:
903                     handleCmd(cmd.msg, cmd.slotId);
904                     break;
905                 case OP_END_SESSION:
906                     handleSessionEnd(cmd.slotId);
907                     break;
908                 }
909             }
910         }
911     }
912 
callDelayedMsg(int slotId)913     private void callDelayedMsg(int slotId) {
914         Message msg = mServiceHandler.obtainMessage();
915         msg.arg1 = OP_DELAYED_MSG;
916         msg.arg2 = slotId;
917         mServiceHandler.sendMessage(msg);
918     }
919 
callSetActivityInstMsg(int inst_type, int slotId, Object obj)920     private void callSetActivityInstMsg(int inst_type, int slotId, Object obj) {
921         Message msg = mServiceHandler.obtainMessage();
922         msg.obj = obj;
923         msg.arg1 = inst_type;
924         msg.arg2 = slotId;
925         mServiceHandler.sendMessage(msg);
926     }
927 
handleSessionEnd(int slotId)928     private void handleSessionEnd(int slotId) {
929         // We should finish all pending activity if receiving END SESSION command.
930         cleanUpInstanceStackBySlot(slotId);
931 
932         mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd;
933         CatLog.d(LOG_TAG, "[handleSessionEnd] - mCurrentCmd changed to mMainCmd!.");
934         mStkContext[slotId].mCurrentMenuCmd = mStkContext[slotId].mMainCmd;
935         CatLog.d(LOG_TAG, "slotId: " + slotId + ", mMenuState: " +
936                 mStkContext[slotId].mMenuState);
937 
938         mStkContext[slotId].mIsInputPending = false;
939         mStkContext[slotId].mIsMenuPending = false;
940         mStkContext[slotId].mIsDialogPending = false;
941         mStkContext[slotId].mNoResponseFromUser = false;
942 
943         if (mStkContext[slotId].mMainCmd == null) {
944             CatLog.d(LOG_TAG, "[handleSessionEnd][mMainCmd is null!]");
945         }
946         mStkContext[slotId].lastSelectedItem = null;
947         mStkContext[slotId].mIsSessionFromUser = false;
948         // In case of SET UP MENU command which removed the app, don't
949         // update the current menu member.
950         if (mStkContext[slotId].mCurrentMenu != null && mStkContext[slotId].mMainCmd != null) {
951             mStkContext[slotId].mCurrentMenu = mStkContext[slotId].mMainCmd.getMenu();
952         }
953         CatLog.d(LOG_TAG, "[handleSessionEnd][mMenuState]" + mStkContext[slotId].mMenuIsVisible);
954 
955         if (StkMenuActivity.STATE_SECONDARY == mStkContext[slotId].mMenuState) {
956             mStkContext[slotId].mMenuState = StkMenuActivity.STATE_MAIN;
957         }
958 
959         // Send a local broadcast as a notice that this service handled the session end event.
960         Intent intent = new Intent(SESSION_ENDED);
961         intent.putExtra(SLOT_ID, slotId);
962         LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
963 
964         if (mStkContext[slotId].mCmdsQ.size() != 0) {
965             callDelayedMsg(slotId);
966         } else {
967             mStkContext[slotId].mCmdInProgress = false;
968         }
969         // In case a launch browser command was just confirmed, launch that url.
970         if (mStkContext[slotId].launchBrowser) {
971             mStkContext[slotId].launchBrowser = false;
972             launchBrowser(mStkContext[slotId].mBrowserSettings);
973         }
974     }
975 
976     // returns true if any Stk related activity already has focus on the screen
isTopOfStack()977     boolean isTopOfStack() {
978         ActivityManager mActivityManager = (ActivityManager) mContext
979                 .getSystemService(ACTIVITY_SERVICE);
980         String currentPackageName = null;
981         List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
982         if (tasks == null || tasks.get(0).topActivity == null) {
983             return false;
984         }
985         currentPackageName = tasks.get(0).topActivity.getPackageName();
986         if (null != currentPackageName) {
987             return currentPackageName.equals(PACKAGE_NAME);
988         }
989         return false;
990     }
991 
992     /**
993      * Get the boolean config from carrier config manager.
994      *
995      * @param key config key defined in CarrierConfigManager
996      * @param slotId slot ID.
997      * @return boolean value of corresponding key.
998      */
getBooleanCarrierConfig(String key, int slotId)999     private boolean getBooleanCarrierConfig(String key, int slotId) {
1000         CarrierConfigManager ccm = (CarrierConfigManager) getSystemService(CARRIER_CONFIG_SERVICE);
1001         SubscriptionManager sm = (SubscriptionManager) getSystemService(
1002                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
1003         PersistableBundle b = null;
1004         if (ccm != null && sm != null) {
1005             SubscriptionInfo info = sm.getActiveSubscriptionInfoForSimSlotIndex(slotId);
1006             if (info != null) {
1007                 b = ccm.getConfigForSubId(info.getSubscriptionId());
1008             }
1009         }
1010         if (b != null) {
1011             return b.getBoolean(key);
1012         }
1013         // Return static default defined in CarrierConfigManager.
1014         return CarrierConfigManager.getDefaultConfig().getBoolean(key);
1015     }
1016 
handleCmd(CatCmdMessage cmdMsg, int slotId)1017     private void handleCmd(CatCmdMessage cmdMsg, int slotId) {
1018 
1019         if (cmdMsg == null) {
1020             return;
1021         }
1022         // save local reference for state tracking.
1023         mStkContext[slotId].mCurrentCmd = cmdMsg;
1024         boolean waitForUsersResponse = true;
1025 
1026         mStkContext[slotId].mIsInputPending = false;
1027         mStkContext[slotId].mIsMenuPending = false;
1028         mStkContext[slotId].mIsDialogPending = false;
1029 
1030         CatLog.d(LOG_TAG,"[handleCmd]" + cmdMsg.getCmdType().name());
1031         switch (cmdMsg.getCmdType()) {
1032         case DISPLAY_TEXT:
1033             TextMessage msg = cmdMsg.geTextMessage();
1034             waitForUsersResponse = msg.responseNeeded;
1035             if (mStkContext[slotId].lastSelectedItem != null) {
1036                 msg.title = mStkContext[slotId].lastSelectedItem;
1037             } else if (mStkContext[slotId].mMainCmd != null){
1038                 if (!getResources().getBoolean(R.bool.show_menu_title_only_on_menu)) {
1039                     msg.title = mStkContext[slotId].mMainCmd.getMenu().title;
1040                 }
1041             }
1042             //If we receive a low priority Display Text and the device is
1043             // not displaying any STK related activity and the screen is not idle
1044             // ( that is, device is in an interactive state), then send a screen busy
1045             // terminal response. Otherwise display the message. The existing
1046             // displayed message shall be updated with the new display text
1047             // proactive command (Refer to ETSI TS 102 384 section 27.22.4.1.4.4.2).
1048             if (!(msg.isHighPriority || mStkContext[slotId].mMenuIsVisible
1049                     || mStkContext[slotId].mDisplayTextDlgIsVisibile || isTopOfStack())) {
1050                 if(!isScreenIdle()) {
1051                     CatLog.d(LOG_TAG, "Screen is not idle");
1052                     sendScreenBusyResponse(slotId);
1053                 } else {
1054                     launchTextDialog(slotId);
1055                 }
1056             } else {
1057                 launchTextDialog(slotId);
1058             }
1059             break;
1060         case SELECT_ITEM:
1061             CatLog.d(LOG_TAG, "SELECT_ITEM +");
1062             mStkContext[slotId].mCurrentMenuCmd = mStkContext[slotId].mCurrentCmd;
1063             mStkContext[slotId].mCurrentMenu = cmdMsg.getMenu();
1064             launchMenuActivity(cmdMsg.getMenu(), slotId);
1065             break;
1066         case SET_UP_MENU:
1067             mStkContext[slotId].mCmdInProgress = false;
1068             mStkContext[slotId].mMainCmd = mStkContext[slotId].mCurrentCmd;
1069             mStkContext[slotId].mCurrentMenuCmd = mStkContext[slotId].mCurrentCmd;
1070             mStkContext[slotId].mCurrentMenu = cmdMsg.getMenu();
1071             CatLog.d(LOG_TAG, "SET_UP_MENU [" + removeMenu(slotId) + "]");
1072 
1073             if (removeMenu(slotId)) {
1074                 int i = 0;
1075                 CatLog.d(LOG_TAG, "removeMenu() - Uninstall App");
1076                 mStkContext[slotId].mCurrentMenu = null;
1077                 mStkContext[slotId].mMainCmd = null;
1078                 //Check other setup menu state. If all setup menu are removed, uninstall apk.
1079                 for (i = PhoneConstants.SIM_ID_1; i < mSimCount; i++) {
1080                     if (i != slotId && mStkContext[i].mSetupMenuState != STATE_NOT_EXIST) {
1081                         CatLog.d(LOG_TAG, "Not Uninstall App:" + i + ","
1082                                 + mStkContext[i].mSetupMenuState);
1083                         break;
1084                     }
1085                 }
1086                 if (i == mSimCount) {
1087                     StkAppInstaller.unInstall(mContext);
1088                 }
1089             } else {
1090                 CatLog.d(LOG_TAG, "install App");
1091                 StkAppInstaller.install(mContext);
1092             }
1093             if (mStkContext[slotId].mMenuIsVisible) {
1094                 launchMenuActivity(null, slotId);
1095             }
1096             break;
1097         case GET_INPUT:
1098         case GET_INKEY:
1099             launchInputActivity(slotId);
1100             break;
1101         case SET_UP_IDLE_MODE_TEXT:
1102             waitForUsersResponse = false;
1103             mStkContext[slotId].mIdleModeTextCmd = mStkContext[slotId].mCurrentCmd;
1104             TextMessage idleModeText = mStkContext[slotId].mCurrentCmd.geTextMessage();
1105             if (idleModeText == null || TextUtils.isEmpty(idleModeText.text)) {
1106                 cancelIdleText(slotId);
1107             }
1108             mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd;
1109             if (mStkContext[slotId].mIdleModeTextCmd != null) {
1110                 if (mStkContext[slotId].mIdleModeTextVisible || isScreenIdle()) {
1111                     CatLog.d(this, "set up idle mode");
1112                     launchIdleText(slotId);
1113                 } else {
1114                     registerProcessObserver();
1115                 }
1116             }
1117             break;
1118         case SEND_DTMF:
1119         case SEND_SMS:
1120         case REFRESH:
1121         case RUN_AT:
1122         case SEND_SS:
1123         case SEND_USSD:
1124         case GET_CHANNEL_STATUS:
1125             waitForUsersResponse = false;
1126             launchEventMessage(slotId);
1127             break;
1128         case LAUNCH_BROWSER:
1129             // The device setup process should not be interrupted by launching browser.
1130             if (Settings.Global.getInt(mContext.getContentResolver(),
1131                     Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
1132                 CatLog.d(this, "The command is not performed if the setup has not been completed.");
1133                 sendScreenBusyResponse(slotId);
1134                 break;
1135             }
1136 
1137             /* Check if Carrier would not want to launch browser */
1138             if (getBooleanCarrierConfig(CarrierConfigManager.KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL,
1139                     slotId)) {
1140                 CatLog.d(this, "Browser is not launched as per carrier.");
1141                 sendResponse(RES_ID_DONE, slotId, true);
1142                 break;
1143             }
1144 
1145             mStkContext[slotId].mBrowserSettings =
1146                     mStkContext[slotId].mCurrentCmd.getBrowserSettings();
1147             if (!isUrlAvailableToLaunchBrowser(mStkContext[slotId].mBrowserSettings)) {
1148                 CatLog.d(this, "Browser url property is not set - send error");
1149                 sendResponse(RES_ID_ERROR, slotId, true);
1150             } else {
1151                 TextMessage alphaId = mStkContext[slotId].mCurrentCmd.geTextMessage();
1152                 if ((alphaId == null) || TextUtils.isEmpty(alphaId.text)) {
1153                     // don't need user confirmation in this case
1154                     // just launch the browser or spawn a new tab
1155                     CatLog.d(this, "user confirmation is not currently needed.\n" +
1156                             "supressing confirmation dialogue and confirming silently...");
1157                     mStkContext[slotId].launchBrowser = true;
1158                     sendResponse(RES_ID_CONFIRM, slotId, true);
1159                 } else {
1160                     launchConfirmationDialog(alphaId, slotId);
1161                 }
1162             }
1163             break;
1164         case SET_UP_CALL:
1165             TextMessage mesg = mStkContext[slotId].mCurrentCmd.getCallSettings().confirmMsg;
1166             if((mesg != null) && (mesg.text == null || mesg.text.length() == 0)) {
1167                 mesg.text = getResources().getString(R.string.default_setup_call_msg);
1168             }
1169             CatLog.d(this, "SET_UP_CALL mesg.text " + mesg.text);
1170             launchConfirmationDialog(mesg, slotId);
1171             break;
1172         case PLAY_TONE:
1173             handlePlayTone(slotId);
1174             break;
1175         case OPEN_CHANNEL:
1176             launchOpenChannelDialog(slotId);
1177             break;
1178         case CLOSE_CHANNEL:
1179         case RECEIVE_DATA:
1180         case SEND_DATA:
1181             TextMessage m = mStkContext[slotId].mCurrentCmd.geTextMessage();
1182 
1183             if ((m != null) && (m.text == null)) {
1184                 switch(cmdMsg.getCmdType()) {
1185                 case CLOSE_CHANNEL:
1186                     m.text = getResources().getString(R.string.default_close_channel_msg);
1187                     break;
1188                 case RECEIVE_DATA:
1189                     m.text = getResources().getString(R.string.default_receive_data_msg);
1190                     break;
1191                 case SEND_DATA:
1192                     m.text = getResources().getString(R.string.default_send_data_msg);
1193                     break;
1194                 }
1195             }
1196             /*
1197              * Display indication in the form of a toast to the user if required.
1198              */
1199             launchEventMessage(slotId);
1200             break;
1201         case SET_UP_EVENT_LIST:
1202             replaceEventList(slotId);
1203             if (isScreenIdle()) {
1204                 CatLog.d(this," Check if IDLE_SCREEN_AVAILABLE_EVENT is present in List");
1205                 checkForSetupEvent(IDLE_SCREEN_AVAILABLE_EVENT, null, slotId);
1206             }
1207             break;
1208         }
1209 
1210         if (!waitForUsersResponse) {
1211             if (mStkContext[slotId].mCmdsQ.size() != 0) {
1212                 callDelayedMsg(slotId);
1213             } else {
1214                 mStkContext[slotId].mCmdInProgress = false;
1215             }
1216         }
1217     }
1218 
1219     @SuppressWarnings("FallThrough")
handleCmdResponse(Bundle args, int slotId)1220     private void handleCmdResponse(Bundle args, int slotId) {
1221         CatLog.d(LOG_TAG, "handleCmdResponse, sim id: " + slotId);
1222         if (mStkContext[slotId].mCurrentCmd == null) {
1223             return;
1224         }
1225 
1226         if (mStkService[slotId] == null) {
1227             mStkService[slotId] = CatService.getInstance(slotId);
1228             if (mStkService[slotId] == null) {
1229                 // This should never happen (we should be responding only to a message
1230                 // that arrived from StkService). It has to exist by this time
1231                 CatLog.d(LOG_TAG, "Exception! mStkService is null when we need to send response.");
1232                 throw new RuntimeException("mStkService is null when we need to send response");
1233             }
1234         }
1235 
1236         CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentCmd);
1237 
1238         // set result code
1239         boolean helpRequired = args.getBoolean(HELP, false);
1240         boolean confirmed    = false;
1241 
1242         switch(args.getInt(RES_ID)) {
1243         case RES_ID_MENU_SELECTION:
1244             CatLog.d(LOG_TAG, "MENU_SELECTION=" + mStkContext[slotId].
1245                     mCurrentMenuCmd.getCmdType());
1246             int menuSelection = args.getInt(MENU_SELECTION);
1247             switch(mStkContext[slotId].mCurrentMenuCmd.getCmdType()) {
1248             case SET_UP_MENU:
1249                 mStkContext[slotId].mIsSessionFromUser = true;
1250                 // Fall through
1251             case SELECT_ITEM:
1252                 mStkContext[slotId].lastSelectedItem = getItemName(menuSelection, slotId);
1253                 if (helpRequired) {
1254                     resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED);
1255                 } else {
1256                     resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ?
1257                             ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK);
1258                 }
1259                 resMsg.setMenuSelection(menuSelection);
1260                 break;
1261             }
1262             break;
1263         case RES_ID_INPUT:
1264             CatLog.d(LOG_TAG, "RES_ID_INPUT");
1265             String input = args.getString(INPUT);
1266             if (input != null && (null != mStkContext[slotId].mCurrentCmd.geInput()) &&
1267                     (mStkContext[slotId].mCurrentCmd.geInput().yesNo)) {
1268                 boolean yesNoSelection = input
1269                         .equals(StkInputActivity.YES_STR_RESPONSE);
1270                 resMsg.setYesNo(yesNoSelection);
1271             } else {
1272                 if (helpRequired) {
1273                     resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED);
1274                 } else {
1275                     resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ?
1276                             ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK);
1277                     resMsg.setInput(input);
1278                 }
1279             }
1280             break;
1281         case RES_ID_CONFIRM:
1282             CatLog.d(this, "RES_ID_CONFIRM");
1283             confirmed = args.getBoolean(CONFIRMATION);
1284             switch (mStkContext[slotId].mCurrentCmd.getCmdType()) {
1285             case DISPLAY_TEXT:
1286                 if (confirmed) {
1287                     resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ?
1288                             ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK);
1289                 } else {
1290                     resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER);
1291                 }
1292                 break;
1293             case LAUNCH_BROWSER:
1294                 resMsg.setResultCode(confirmed ? ResultCode.OK
1295                         : ResultCode.UICC_SESSION_TERM_BY_USER);
1296                 if (confirmed) {
1297                     mStkContext[slotId].launchBrowser = true;
1298                     mStkContext[slotId].mBrowserSettings =
1299                             mStkContext[slotId].mCurrentCmd.getBrowserSettings();
1300                 }
1301                 break;
1302             case SET_UP_CALL:
1303                 resMsg.setResultCode(ResultCode.OK);
1304                 resMsg.setConfirmation(confirmed);
1305                 if (confirmed) {
1306                     launchEventMessage(slotId,
1307                             mStkContext[slotId].mCurrentCmd.getCallSettings().callMsg);
1308                 }
1309                 break;
1310             }
1311             break;
1312         case RES_ID_DONE:
1313             resMsg.setResultCode(ResultCode.OK);
1314             break;
1315         case RES_ID_BACKWARD:
1316             CatLog.d(LOG_TAG, "RES_ID_BACKWARD");
1317             resMsg.setResultCode(ResultCode.BACKWARD_MOVE_BY_USER);
1318             break;
1319         case RES_ID_END_SESSION:
1320             CatLog.d(LOG_TAG, "RES_ID_END_SESSION");
1321             resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER);
1322             break;
1323         case RES_ID_TIMEOUT:
1324             CatLog.d(LOG_TAG, "RES_ID_TIMEOUT");
1325             // GCF test-case 27.22.4.1.1 Expected Sequence 1.5 (DISPLAY TEXT,
1326             // Clear message after delay, successful) expects result code OK.
1327             // If the command qualifier specifies no user response is required
1328             // then send OK instead of NO_RESPONSE_FROM_USER
1329             if ((mStkContext[slotId].mCurrentCmd.getCmdType().value() ==
1330                     AppInterface.CommandType.DISPLAY_TEXT.value())
1331                     && (mStkContext[slotId].mCurrentCmd.geTextMessage().userClear == false)) {
1332                 resMsg.setResultCode(ResultCode.OK);
1333             } else {
1334                 resMsg.setResultCode(ResultCode.NO_RESPONSE_FROM_USER);
1335             }
1336             break;
1337         case RES_ID_CHOICE:
1338             int choice = args.getInt(CHOICE);
1339             CatLog.d(this, "User Choice=" + choice);
1340             switch (choice) {
1341                 case YES:
1342                     resMsg.setResultCode(ResultCode.OK);
1343                     confirmed = true;
1344                     break;
1345                 case NO:
1346                     resMsg.setResultCode(ResultCode.USER_NOT_ACCEPT);
1347                     break;
1348             }
1349 
1350             if (mStkContext[slotId].mCurrentCmd.getCmdType().value() ==
1351                     AppInterface.CommandType.OPEN_CHANNEL.value()) {
1352                 resMsg.setConfirmation(confirmed);
1353             }
1354             break;
1355         case RES_ID_ERROR:
1356             CatLog.d(LOG_TAG, "RES_ID_ERROR");
1357             switch (mStkContext[slotId].mCurrentCmd.getCmdType()) {
1358             case LAUNCH_BROWSER:
1359                 resMsg.setResultCode(ResultCode.LAUNCH_BROWSER_ERROR);
1360                 break;
1361             }
1362             break;
1363         default:
1364             CatLog.d(LOG_TAG, "Unknown result id");
1365             return;
1366         }
1367 
1368         switch (args.getInt(RES_ID)) {
1369             case RES_ID_MENU_SELECTION:
1370             case RES_ID_INPUT:
1371             case RES_ID_CONFIRM:
1372             case RES_ID_CHOICE:
1373             case RES_ID_BACKWARD:
1374             case RES_ID_END_SESSION:
1375                 mStkContext[slotId].mNoResponseFromUser = false;
1376                 break;
1377             case RES_ID_TIMEOUT:
1378                 cancelNotificationOnKeyguard(slotId);
1379                 mStkContext[slotId].mNoResponseFromUser = true;
1380                 break;
1381             default:
1382                 // The other IDs cannot be used to judge if there is no response from user.
1383                 break;
1384         }
1385 
1386         if (null != mStkContext[slotId].mCurrentCmd &&
1387                 null != mStkContext[slotId].mCurrentCmd.getCmdType()) {
1388             CatLog.d(LOG_TAG, "handleCmdResponse- cmdName[" +
1389                     mStkContext[slotId].mCurrentCmd.getCmdType().name() + "]");
1390         }
1391         mStkService[slotId].onCmdResponse(resMsg);
1392     }
1393 
1394     /**
1395      * Returns 0 or FLAG_ACTIVITY_NO_USER_ACTION, 0 means the user initiated the action.
1396      *
1397      * @param userAction If the userAction is yes then we always return 0 otherwise
1398      * mMenuIsVisible is used to determine what to return. If mMenuIsVisible is true
1399      * then we are the foreground app and we'll return 0 as from our perspective a
1400      * user action did cause. If it's false than we aren't the foreground app and
1401      * FLAG_ACTIVITY_NO_USER_ACTION is returned.
1402      *
1403      * @return 0 or FLAG_ACTIVITY_NO_USER_ACTION
1404      */
getFlagActivityNoUserAction(InitiatedByUserAction userAction, int slotId)1405     private int getFlagActivityNoUserAction(InitiatedByUserAction userAction, int slotId) {
1406         return ((userAction == InitiatedByUserAction.yes) | mStkContext[slotId].mMenuIsVisible)
1407                 ? 0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION;
1408     }
1409     /**
1410      * This method is used for cleaning up pending instances in stack.
1411      * No terminal response will be sent for pending instances.
1412      */
cleanUpInstanceStackBySlot(int slotId)1413     private void cleanUpInstanceStackBySlot(int slotId) {
1414         Activity activity = mStkContext[slotId].getPendingActivityInstance();
1415         Activity dialog = mStkContext[slotId].getPendingDialogInstance();
1416         CatLog.d(LOG_TAG, "cleanUpInstanceStackBySlot slotId: " + slotId);
1417         if (activity != null) {
1418             if (mStkContext[slotId].mCurrentCmd != null) {
1419                 CatLog.d(LOG_TAG, "current cmd type: " +
1420                         mStkContext[slotId].mCurrentCmd.getCmdType());
1421                 if (mStkContext[slotId].mCurrentCmd.getCmdType().value()
1422                         == AppInterface.CommandType.GET_INPUT.value()
1423                         || mStkContext[slotId].mCurrentCmd.getCmdType().value()
1424                         == AppInterface.CommandType.GET_INKEY.value()) {
1425                     mStkContext[slotId].mIsInputPending = true;
1426                 } else if (mStkContext[slotId].mCurrentCmd.getCmdType().value()
1427                         == AppInterface.CommandType.SET_UP_MENU.value()
1428                         || mStkContext[slotId].mCurrentCmd.getCmdType().value()
1429                         == AppInterface.CommandType.SELECT_ITEM.value()) {
1430                     mStkContext[slotId].mIsMenuPending = true;
1431                 }
1432             }
1433             CatLog.d(LOG_TAG, "finish pending activity.");
1434             activity.finish();
1435             mStkContext[slotId].mActivityInstance = null;
1436         }
1437         if (dialog != null) {
1438             CatLog.d(LOG_TAG, "finish pending dialog.");
1439             mStkContext[slotId].mIsDialogPending = true;
1440             dialog.finish();
1441             mStkContext[slotId].mDialogInstance = null;
1442         }
1443     }
1444     /**
1445      * This method is used for restoring pending instances from stack.
1446      */
restoreInstanceFromStackBySlot(int slotId)1447     private void restoreInstanceFromStackBySlot(int slotId) {
1448         AppInterface.CommandType cmdType = mStkContext[slotId].mCurrentCmd.getCmdType();
1449 
1450         CatLog.d(LOG_TAG, "restoreInstanceFromStackBySlot cmdType : " + cmdType);
1451         switch(cmdType) {
1452             case GET_INPUT:
1453             case GET_INKEY:
1454                 launchInputActivity(slotId);
1455                 //Set mMenuIsVisible to true for showing main menu for
1456                 //following session end command.
1457                 mStkContext[slotId].mMenuIsVisible = true;
1458             break;
1459             case DISPLAY_TEXT:
1460                 launchTextDialog(slotId);
1461             break;
1462             case LAUNCH_BROWSER:
1463                 launchConfirmationDialog(mStkContext[slotId].mCurrentCmd.geTextMessage(),
1464                         slotId);
1465             break;
1466             case OPEN_CHANNEL:
1467                 launchOpenChannelDialog(slotId);
1468             break;
1469             case SET_UP_CALL:
1470                 launchConfirmationDialog(mStkContext[slotId].mCurrentCmd.getCallSettings().
1471                         confirmMsg, slotId);
1472             break;
1473             case SET_UP_MENU:
1474             case SELECT_ITEM:
1475                 launchMenuActivity(null, slotId);
1476             break;
1477         default:
1478             break;
1479         }
1480     }
1481 
1482     @Override
startActivity(Intent intent)1483     public void startActivity(Intent intent) {
1484         int slotId = intent.getIntExtra(SLOT_ID, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
1485         // Close the dialog displayed for DISPLAY TEXT command with an immediate response object
1486         // before new dialog is displayed.
1487         if (SubscriptionManager.isValidSlotIndex(slotId)) {
1488             Activity dialog = mStkContext[slotId].getImmediateDialogInstance();
1489             if (dialog != null) {
1490                 CatLog.d(LOG_TAG, "finish dialog for immediate response.");
1491                 dialog.finish();
1492             }
1493         }
1494         super.startActivity(intent);
1495     }
1496 
launchMenuActivity(Menu menu, int slotId)1497     private void launchMenuActivity(Menu menu, int slotId) {
1498         Intent newIntent = new Intent(Intent.ACTION_VIEW);
1499         String targetActivity = STK_MENU_ACTIVITY_NAME;
1500         String uriString = STK_MENU_URI + System.currentTimeMillis();
1501         //Set unique URI to create a new instance of activity for different slotId.
1502         Uri uriData = Uri.parse(uriString);
1503 
1504         CatLog.d(LOG_TAG, "launchMenuActivity, slotId: " + slotId + " , " +
1505                 uriData.toString() + " , " + mStkContext[slotId].mOpCode + ", "
1506                 + mStkContext[slotId].mMenuState);
1507         newIntent.setClassName(PACKAGE_NAME, targetActivity);
1508         int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK;
1509 
1510         if (menu == null) {
1511             // We assume this was initiated by the user pressing the tool kit icon
1512             intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.yes, slotId);
1513             //If the last pending menu is secondary menu, "STATE" should be "STATE_SECONDARY".
1514             //Otherwise, it should be "STATE_MAIN".
1515             if (mStkContext[slotId].mOpCode == OP_LAUNCH_APP &&
1516                     mStkContext[slotId].mMenuState == StkMenuActivity.STATE_SECONDARY) {
1517                 newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY);
1518             } else {
1519                 newIntent.putExtra("STATE", StkMenuActivity.STATE_MAIN);
1520                 mStkContext[slotId].mMenuState = StkMenuActivity.STATE_MAIN;
1521             }
1522         } else {
1523             // We don't know and we'll let getFlagActivityNoUserAction decide.
1524             intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId);
1525             newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY);
1526             mStkContext[slotId].mMenuState = StkMenuActivity.STATE_SECONDARY;
1527         }
1528         newIntent.putExtra(SLOT_ID, slotId);
1529         newIntent.setData(uriData);
1530         newIntent.setFlags(intentFlags);
1531         startActivity(newIntent);
1532     }
1533 
launchInputActivity(int slotId)1534     private void launchInputActivity(int slotId) {
1535         Intent newIntent = new Intent(Intent.ACTION_VIEW);
1536         String targetActivity = STK_INPUT_ACTIVITY_NAME;
1537         String uriString = STK_INPUT_URI + System.currentTimeMillis();
1538         //Set unique URI to create a new instance of activity for different slotId.
1539         Uri uriData = Uri.parse(uriString);
1540         Input input = mStkContext[slotId].mCurrentCmd.geInput();
1541 
1542         CatLog.d(LOG_TAG, "launchInputActivity, slotId: " + slotId);
1543         newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1544                             | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId));
1545         newIntent.setClassName(PACKAGE_NAME, targetActivity);
1546         newIntent.putExtra("INPUT", input);
1547         newIntent.putExtra(SLOT_ID, slotId);
1548         newIntent.setData(uriData);
1549 
1550         if (input != null) {
1551             notifyUserIfNecessary(slotId, input.text);
1552         }
1553         startActivity(newIntent);
1554     }
1555 
launchTextDialog(int slotId)1556     private void launchTextDialog(int slotId) {
1557         CatLog.d(LOG_TAG, "launchTextDialog, slotId: " + slotId);
1558         Intent newIntent = new Intent();
1559         String targetActivity = STK_DIALOG_ACTIVITY_NAME;
1560         int action = getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId);
1561         String uriString = STK_DIALOG_URI + System.currentTimeMillis();
1562         //Set unique URI to create a new instance of activity for different slotId.
1563         Uri uriData = Uri.parse(uriString);
1564         TextMessage textMessage = mStkContext[slotId].mCurrentCmd.geTextMessage();
1565 
1566         newIntent.setClassName(PACKAGE_NAME, targetActivity);
1567         newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1568                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
1569                 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId));
1570         newIntent.setData(uriData);
1571         newIntent.putExtra("TEXT", textMessage);
1572         newIntent.putExtra(SLOT_ID, slotId);
1573 
1574         if (textMessage != null) {
1575             notifyUserIfNecessary(slotId, textMessage.text);
1576         }
1577         startActivity(newIntent);
1578         // For display texts with immediate response, send the terminal response
1579         // immediately. responseNeeded will be false, if display text command has
1580         // the immediate response tlv.
1581         if (!mStkContext[slotId].mCurrentCmd.geTextMessage().responseNeeded) {
1582             sendResponse(RES_ID_CONFIRM, slotId, true);
1583         }
1584     }
1585 
notifyUserIfNecessary(int slotId, String message)1586     private void notifyUserIfNecessary(int slotId, String message) {
1587         createAllChannels();
1588 
1589         if (mStkContext[slotId].mNoResponseFromUser) {
1590             // No response from user was observed in the current session.
1591             // Do nothing in that case in order to avoid turning on the screen again and again
1592             // when the card repeatedly sends the same command in its retry procedure.
1593             return;
1594         }
1595 
1596         PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
1597 
1598         if (((KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE)).isKeyguardLocked()) {
1599             // Display the notification on the keyguard screen
1600             // if user cannot see the message from the card right now because of it.
1601             // The notification can be dismissed if user removed the keyguard screen.
1602             launchNotificationOnKeyguard(slotId, message);
1603         } else if (!(pm.isInteractive() && isTopOfStack())) {
1604             // User might be doing something but it is not related to the SIM Toolkit.
1605             // Play the tone and do vibration in order to attract user's attention.
1606             // User will see the input screen or the dialog soon in this case.
1607             NotificationChannel channel = mNotificationManager
1608                     .getNotificationChannel(STK_NOTIFICATION_CHANNEL_ID);
1609             Uri uri = channel.getSound();
1610             if (uri != null && !Uri.EMPTY.equals(uri)
1611                     && (NotificationManager.IMPORTANCE_LOW) < channel.getImportance()) {
1612                 RingtoneManager.getRingtone(getApplicationContext(), uri).play();
1613             }
1614             long[] pattern = channel.getVibrationPattern();
1615             if (pattern != null && channel.shouldVibrate()) {
1616                 ((Vibrator) this.getSystemService(Context.VIBRATOR_SERVICE))
1617                         .vibrate(pattern, -1);
1618             }
1619         }
1620 
1621         // Turn on the screen.
1622         PowerManager.WakeLock wakelock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
1623                 | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, LOG_TAG);
1624         wakelock.acquire();
1625         wakelock.release();
1626     }
1627 
launchNotificationOnKeyguard(int slotId, String message)1628     private void launchNotificationOnKeyguard(int slotId, String message) {
1629         Notification.Builder builder = new Notification.Builder(this, STK_NOTIFICATION_CHANNEL_ID);
1630 
1631         builder.setStyle(new Notification.BigTextStyle(builder).bigText(message));
1632         builder.setContentText(message);
1633 
1634         Menu menu = getMainMenu(slotId);
1635         if (menu == null || TextUtils.isEmpty(menu.title)) {
1636             builder.setContentTitle(getResources().getString(R.string.app_name));
1637         } else {
1638             builder.setContentTitle(menu.title);
1639         }
1640 
1641         builder.setSmallIcon(com.android.internal.R.drawable.stat_notify_sim_toolkit);
1642         builder.setOngoing(true);
1643         builder.setOnlyAlertOnce(true);
1644         builder.setColor(getResources().getColor(
1645                 com.android.internal.R.color.system_notification_accent_color));
1646 
1647         registerUserPresentReceiver();
1648         mNotificationManager.notify(getNotificationId(NOTIFICATION_ON_KEYGUARD, slotId),
1649                 builder.build());
1650         mStkContext[slotId].mNotificationOnKeyguard = true;
1651     }
1652 
cancelNotificationOnKeyguard(int slotId)1653     private void cancelNotificationOnKeyguard(int slotId) {
1654         mNotificationManager.cancel(getNotificationId(NOTIFICATION_ON_KEYGUARD, slotId));
1655         mStkContext[slotId].mNotificationOnKeyguard = false;
1656         unregisterUserPresentReceiver(slotId);
1657     }
1658 
registerUserPresentReceiver()1659     private synchronized void registerUserPresentReceiver() {
1660         if (mUserPresentReceiver == null) {
1661             mUserPresentReceiver = new BroadcastReceiver() {
1662                 @Override public void onReceive(Context context, Intent intent) {
1663                     if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
1664                         for (int slot = 0; slot < mSimCount; slot++) {
1665                             cancelNotificationOnKeyguard(slot);
1666                         }
1667                     }
1668                 }
1669             };
1670             registerReceiver(mUserPresentReceiver, new IntentFilter(Intent.ACTION_USER_PRESENT));
1671         }
1672     }
1673 
unregisterUserPresentReceiver(int slotId)1674     private synchronized void unregisterUserPresentReceiver(int slotId) {
1675         if (mUserPresentReceiver != null) {
1676             for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
1677                 if (slot != slotId) {
1678                     if (mStkContext[slot].mNotificationOnKeyguard) {
1679                         // The broadcast receiver is still necessary for other SIM card.
1680                         return;
1681                     }
1682                 }
1683             }
1684             unregisterReceiver(mUserPresentReceiver);
1685             mUserPresentReceiver = null;
1686         }
1687     }
1688 
getNotificationId(int notificationType, int slotId)1689     private int getNotificationId(int notificationType, int slotId) {
1690         return getNotificationId(slotId) + (notificationType * mSimCount);
1691     }
1692 
1693     /**
1694      * Checks whether the dialog exists as the top activity of this task.
1695      *
1696      * @return true if the top activity of this task is the dialog.
1697      */
isStkDialogActivated()1698     public boolean isStkDialogActivated() {
1699         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
1700         ComponentName componentName = am.getAppTasks().get(0).getTaskInfo().topActivity;
1701         if (componentName != null) {
1702             String[] split = componentName.getClassName().split(Pattern.quote("."));
1703             String topActivity = split[split.length - 1];
1704             CatLog.d(LOG_TAG, "Top activity: " + topActivity);
1705             if (TextUtils.equals(topActivity, StkDialogActivity.class.getSimpleName())) {
1706                 return true;
1707             }
1708         }
1709         return false;
1710     }
1711 
replaceEventList(int slotId)1712     private void replaceEventList(int slotId) {
1713         if (mStkContext[slotId].mSetupEventListSettings != null) {
1714             for (int current : mStkContext[slotId].mSetupEventListSettings.eventList) {
1715                 if (current != INVALID_SETUP_EVENT) {
1716                     // Cancel the event notification if it is not listed in the new event list.
1717                     if ((mStkContext[slotId].mCurrentCmd.getSetEventList() == null)
1718                             || !findEvent(current, mStkContext[slotId].mCurrentCmd
1719                             .getSetEventList().eventList)) {
1720                         unregisterEvent(current, slotId);
1721                     }
1722                 }
1723             }
1724         }
1725         mStkContext[slotId].mSetupEventListSettings
1726                 = mStkContext[slotId].mCurrentCmd.getSetEventList();
1727         mStkContext[slotId].mCurrentSetupEventCmd = mStkContext[slotId].mCurrentCmd;
1728         mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd;
1729         registerEvents(slotId);
1730     }
1731 
findEvent(int event, int[] eventList)1732     private boolean findEvent(int event, int[] eventList) {
1733         for (int content : eventList) {
1734             if (content == event) return true;
1735         }
1736         return false;
1737     }
1738 
unregisterEvent(int event, int slotId)1739     private void unregisterEvent(int event, int slotId) {
1740         for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
1741             if (slot != slotId) {
1742                 if (mStkContext[slot].mSetupEventListSettings != null) {
1743                     if (findEvent(event, mStkContext[slot].mSetupEventListSettings.eventList)) {
1744                         // The specified event shall never be canceled
1745                         // if there is any other SIM card which requests the event.
1746                         return;
1747                     }
1748                 }
1749             }
1750         }
1751 
1752         switch (event) {
1753             case USER_ACTIVITY_EVENT:
1754                 unregisterUserActivityReceiver();
1755                 break;
1756             case IDLE_SCREEN_AVAILABLE_EVENT:
1757                 unregisterProcessObserver(AppInterface.CommandType.SET_UP_EVENT_LIST, slotId);
1758                 break;
1759             case LANGUAGE_SELECTION_EVENT:
1760                 unregisterLocaleChangeReceiver();
1761                 break;
1762             default:
1763                 break;
1764         }
1765     }
1766 
registerEvents(int slotId)1767     private void registerEvents(int slotId) {
1768         if (mStkContext[slotId].mSetupEventListSettings == null) {
1769             return;
1770         }
1771         for (int event : mStkContext[slotId].mSetupEventListSettings.eventList) {
1772             switch (event) {
1773                 case USER_ACTIVITY_EVENT:
1774                     registerUserActivityReceiver();
1775                     break;
1776                 case IDLE_SCREEN_AVAILABLE_EVENT:
1777                     registerProcessObserver();
1778                     break;
1779                 case LANGUAGE_SELECTION_EVENT:
1780                     registerLocaleChangeReceiver();
1781                     break;
1782                 default:
1783                     break;
1784             }
1785         }
1786     }
1787 
registerUserActivityReceiver()1788     private synchronized void registerUserActivityReceiver() {
1789         if (mUserActivityReceiver == null) {
1790             mUserActivityReceiver = new BroadcastReceiver() {
1791                 @Override public void onReceive(Context context, Intent intent) {
1792                     if (WindowManagerPolicyConstants.ACTION_USER_ACTIVITY_NOTIFICATION.equals(
1793                             intent.getAction())) {
1794                         Message message = mServiceHandler.obtainMessage();
1795                         message.arg1 = OP_USER_ACTIVITY;
1796                         mServiceHandler.sendMessage(message);
1797                         unregisterUserActivityReceiver();
1798                     }
1799                 }
1800             };
1801             registerReceiver(mUserActivityReceiver, new IntentFilter(
1802                     WindowManagerPolicyConstants.ACTION_USER_ACTIVITY_NOTIFICATION));
1803             try {
1804                 IWindowManager wm = IWindowManager.Stub.asInterface(
1805                         ServiceManager.getService(Context.WINDOW_SERVICE));
1806                 wm.requestUserActivityNotification();
1807             } catch (RemoteException e) {
1808                 CatLog.e(this, "failed to init WindowManager:" + e);
1809             }
1810         }
1811     }
1812 
unregisterUserActivityReceiver()1813     private synchronized void unregisterUserActivityReceiver() {
1814         if (mUserActivityReceiver != null) {
1815             unregisterReceiver(mUserActivityReceiver);
1816             mUserActivityReceiver = null;
1817         }
1818     }
1819 
registerProcessObserver()1820     private synchronized void registerProcessObserver() {
1821         if (mProcessObserver == null) {
1822             try {
1823                 IProcessObserver.Stub observer = new IProcessObserver.Stub() {
1824                     @Override
1825                     public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
1826                         if (isScreenIdle()) {
1827                             Message message = mServiceHandler.obtainMessage();
1828                             message.arg1 = OP_IDLE_SCREEN;
1829                             mServiceHandler.sendMessage(message);
1830                             unregisterProcessObserver();
1831                         }
1832                     }
1833 
1834                     @Override
1835                     public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
1836                     }
1837 
1838                     @Override
1839                     public void onProcessDied(int pid, int uid) {
1840                     }
1841                 };
1842                 ActivityManagerNative.getDefault().registerProcessObserver(observer);
1843                 CatLog.d(this, "Started to observe the foreground activity");
1844                 mProcessObserver = observer;
1845             } catch (RemoteException e) {
1846                 CatLog.d(this, "Failed to register the process observer");
1847             }
1848         }
1849     }
1850 
unregisterProcessObserver(AppInterface.CommandType command, int slotId)1851     private void unregisterProcessObserver(AppInterface.CommandType command, int slotId) {
1852         // Check if there is any pending command which still needs the process observer
1853         // except for the current command and slot.
1854         for (int slot = PhoneConstants.SIM_ID_1; slot < mSimCount; slot++) {
1855             if (command != AppInterface.CommandType.SET_UP_IDLE_MODE_TEXT || slot != slotId) {
1856                 if (mStkContext[slot].mIdleModeTextCmd != null
1857                         && !mStkContext[slot].mIdleModeTextVisible) {
1858                     // Keep the process observer registered
1859                     // as there is an idle mode text which has not been visible yet.
1860                     return;
1861                 }
1862             }
1863             if (command != AppInterface.CommandType.SET_UP_EVENT_LIST || slot != slotId) {
1864                 if (mStkContext[slot].mSetupEventListSettings != null) {
1865                     if (findEvent(IDLE_SCREEN_AVAILABLE_EVENT,
1866                                 mStkContext[slot].mSetupEventListSettings.eventList)) {
1867                         // Keep the process observer registered
1868                         // as there is a SIM card which still want IDLE SCREEN AVAILABLE event.
1869                         return;
1870                     }
1871                 }
1872             }
1873         }
1874         unregisterProcessObserver();
1875     }
1876 
unregisterProcessObserver()1877     private synchronized void unregisterProcessObserver() {
1878         if (mProcessObserver != null) {
1879             try {
1880                 ActivityManagerNative.getDefault().unregisterProcessObserver(mProcessObserver);
1881                 CatLog.d(this, "Stopped to observe the foreground activity");
1882                 mProcessObserver = null;
1883             } catch (RemoteException e) {
1884                 CatLog.d(this, "Failed to unregister the process observer");
1885             }
1886         }
1887     }
1888 
registerLocaleChangeReceiver()1889     private synchronized void registerLocaleChangeReceiver() {
1890         if (mLocaleChangeReceiver == null) {
1891             mLocaleChangeReceiver = new BroadcastReceiver() {
1892                 @Override public void onReceive(Context context, Intent intent) {
1893                     if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
1894                         Message message = mServiceHandler.obtainMessage();
1895                         message.arg1 = OP_LOCALE_CHANGED;
1896                         mServiceHandler.sendMessage(message);
1897                     }
1898                 }
1899             };
1900             registerReceiver(mLocaleChangeReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
1901         }
1902     }
1903 
unregisterLocaleChangeReceiver()1904     private synchronized void unregisterLocaleChangeReceiver() {
1905         if (mLocaleChangeReceiver != null) {
1906             unregisterReceiver(mLocaleChangeReceiver);
1907             mLocaleChangeReceiver = null;
1908         }
1909     }
1910 
sendSetUpEventResponse(int event, byte[] addedInfo, int slotId)1911     private void sendSetUpEventResponse(int event, byte[] addedInfo, int slotId) {
1912         CatLog.d(this, "sendSetUpEventResponse: event : " + event + "slotId = " + slotId);
1913 
1914         if (mStkContext[slotId].mCurrentSetupEventCmd == null){
1915             CatLog.e(this, "mCurrentSetupEventCmd is null");
1916             return;
1917         }
1918 
1919         CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentSetupEventCmd);
1920 
1921         resMsg.setResultCode(ResultCode.OK);
1922         resMsg.setEventDownload(event, addedInfo);
1923 
1924         mStkService[slotId].onCmdResponse(resMsg);
1925     }
1926 
checkForSetupEvent(int event, Bundle args, int slotId)1927     private void checkForSetupEvent(int event, Bundle args, int slotId) {
1928         boolean eventPresent = false;
1929         byte[] addedInfo = null;
1930         CatLog.d(this, "Event :" + event);
1931 
1932         if (mStkContext[slotId].mSetupEventListSettings != null) {
1933             /* Checks if the event is present in the EventList updated by last
1934              * SetupEventList Proactive Command */
1935             for (int i : mStkContext[slotId].mSetupEventListSettings.eventList) {
1936                  if (event == i) {
1937                      eventPresent =  true;
1938                      break;
1939                  }
1940             }
1941 
1942             /* If Event is present send the response to ICC */
1943             if (eventPresent == true) {
1944                 CatLog.d(this, " Event " + event + "exists in the EventList");
1945 
1946                 switch (event) {
1947                     case USER_ACTIVITY_EVENT:
1948                     case IDLE_SCREEN_AVAILABLE_EVENT:
1949                         sendSetUpEventResponse(event, addedInfo, slotId);
1950                         removeSetUpEvent(event, slotId);
1951                         break;
1952                     case LANGUAGE_SELECTION_EVENT:
1953                         String language =  mContext
1954                                 .getResources().getConfiguration().locale.getLanguage();
1955                         CatLog.d(this, "language: " + language);
1956                         // Each language code is a pair of alpha-numeric characters.
1957                         // Each alpha-numeric character shall be coded on one byte
1958                         // using the SMS default 7-bit coded alphabet
1959                         addedInfo = GsmAlphabet.stringToGsm8BitPacked(language);
1960                         sendSetUpEventResponse(event, addedInfo, slotId);
1961                         break;
1962                     default:
1963                         break;
1964                 }
1965             } else {
1966                 CatLog.e(this, " Event does not exist in the EventList");
1967             }
1968         } else {
1969             CatLog.e(this, "SetupEventList is not received. Ignoring the event: " + event);
1970         }
1971     }
1972 
removeSetUpEvent(int event, int slotId)1973     private void removeSetUpEvent(int event, int slotId) {
1974         CatLog.d(this, "Remove Event :" + event);
1975 
1976         if (mStkContext[slotId].mSetupEventListSettings != null) {
1977             /*
1978              * Make new  Eventlist without the event
1979              */
1980             for (int i = 0; i < mStkContext[slotId].mSetupEventListSettings.eventList.length; i++) {
1981                 if (event == mStkContext[slotId].mSetupEventListSettings.eventList[i]) {
1982                     mStkContext[slotId].mSetupEventListSettings.eventList[i] = INVALID_SETUP_EVENT;
1983 
1984                     switch (event) {
1985                         case USER_ACTIVITY_EVENT:
1986                             // The broadcast receiver can be unregistered
1987                             // as the event has already been sent to the card.
1988                             unregisterUserActivityReceiver();
1989                             break;
1990                         case IDLE_SCREEN_AVAILABLE_EVENT:
1991                             // The process observer can be unregistered
1992                             // as the idle screen has already been available.
1993                             unregisterProcessObserver();
1994                             break;
1995                         default:
1996                             break;
1997                     }
1998                     break;
1999                 }
2000             }
2001         }
2002     }
2003 
launchEventMessage(int slotId)2004     private void launchEventMessage(int slotId) {
2005         launchEventMessage(slotId, mStkContext[slotId].mCurrentCmd.geTextMessage());
2006     }
2007 
launchEventMessage(int slotId, TextMessage msg)2008     private void launchEventMessage(int slotId, TextMessage msg) {
2009         if (msg == null || msg.text == null || (msg.text != null && msg.text.length() == 0)) {
2010             CatLog.d(LOG_TAG, "launchEventMessage return");
2011             return;
2012         }
2013 
2014         Toast toast = new Toast(mContext.getApplicationContext());
2015         LayoutInflater inflate = (LayoutInflater) mContext
2016                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2017         View v = inflate.inflate(R.layout.stk_event_msg, null);
2018         TextView tv = (TextView) v
2019                 .findViewById(com.android.internal.R.id.message);
2020         ImageView iv = (ImageView) v
2021                 .findViewById(com.android.internal.R.id.icon);
2022         if (msg.icon != null) {
2023             iv.setImageBitmap(msg.icon);
2024         } else {
2025             iv.setVisibility(View.GONE);
2026         }
2027         /* In case of 'self explanatory' stkapp should display the specified
2028          * icon in proactive command (but not the alpha string).
2029          * If icon is non-self explanatory and if the icon could not be displayed
2030          * then alpha string or text data should be displayed
2031          * Ref: ETSI 102.223,section 6.5.4
2032          */
2033         if (mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ||
2034                 msg.icon == null || !msg.iconSelfExplanatory) {
2035             tv.setText(msg.text);
2036         }
2037 
2038         toast.setView(v);
2039         toast.setDuration(Toast.LENGTH_LONG);
2040         toast.setGravity(Gravity.BOTTOM, 0, 0);
2041         toast.show();
2042     }
2043 
launchConfirmationDialog(TextMessage msg, int slotId)2044     private void launchConfirmationDialog(TextMessage msg, int slotId) {
2045         msg.title = mStkContext[slotId].lastSelectedItem;
2046         Intent newIntent = new Intent();
2047         String targetActivity = STK_DIALOG_ACTIVITY_NAME;
2048         String uriString = STK_DIALOG_URI + System.currentTimeMillis();
2049         //Set unique URI to create a new instance of activity for different slotId.
2050         Uri uriData = Uri.parse(uriString);
2051 
2052         newIntent.setClassName(this, targetActivity);
2053         newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2054                 | Intent.FLAG_ACTIVITY_NO_HISTORY
2055                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
2056                 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId));
2057         newIntent.putExtra("TEXT", msg);
2058         newIntent.putExtra(SLOT_ID, slotId);
2059         newIntent.setData(uriData);
2060         startActivity(newIntent);
2061     }
2062 
launchBrowser(BrowserSettings settings)2063     private void launchBrowser(BrowserSettings settings) {
2064         if (settings == null) {
2065             return;
2066         }
2067 
2068         Uri data = null;
2069         String url;
2070         if (settings.url == null) {
2071             // if the command did not contain a URL,
2072             // launch the browser to the default homepage.
2073             CatLog.d(this, "no url data provided by proactive command." +
2074                        " launching browser with stk default URL ... ");
2075             url = SystemProperties.get(STK_BROWSER_DEFAULT_URL_SYSPROP,
2076                     "http://www.google.com");
2077         } else {
2078             CatLog.d(this, "launch browser command has attached url = " + settings.url);
2079             url = settings.url;
2080         }
2081 
2082         if (url.startsWith("http://") || url.startsWith("https://")) {
2083             data = Uri.parse(url);
2084             CatLog.d(this, "launching browser with url = " + url);
2085         } else {
2086             String modifiedUrl = "http://" + url;
2087             data = Uri.parse(modifiedUrl);
2088             CatLog.d(this, "launching browser with modified url = " + modifiedUrl);
2089         }
2090 
2091         Intent intent = new Intent(Intent.ACTION_VIEW);
2092         intent.setData(data);
2093         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2094         switch (settings.mode) {
2095         case USE_EXISTING_BROWSER:
2096             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
2097             break;
2098         case LAUNCH_NEW_BROWSER:
2099             intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
2100             break;
2101         case LAUNCH_IF_NOT_ALREADY_LAUNCHED:
2102             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
2103             break;
2104         }
2105         // start browser activity
2106         startActivity(intent);
2107         // a small delay, let the browser start, before processing the next command.
2108         // this is good for scenarios where a related DISPLAY TEXT command is
2109         // followed immediately.
2110         try {
2111             Thread.sleep(3000);
2112         } catch (InterruptedException e) {}
2113     }
2114 
cancelIdleText(int slotId)2115     private void cancelIdleText(int slotId) {
2116         unregisterProcessObserver(AppInterface.CommandType.SET_UP_IDLE_MODE_TEXT, slotId);
2117         mNotificationManager.cancel(getNotificationId(slotId));
2118         mStkContext[slotId].mIdleModeTextCmd = null;
2119         mStkContext[slotId].mIdleModeTextVisible = false;
2120     }
2121 
launchIdleText(int slotId)2122     private void launchIdleText(int slotId) {
2123         TextMessage msg = mStkContext[slotId].mIdleModeTextCmd.geTextMessage();
2124 
2125         if (msg != null && !TextUtils.isEmpty(msg.text)) {
2126             CatLog.d(LOG_TAG, "launchIdleText - text[" + msg.text
2127                     + "] iconSelfExplanatory[" + msg.iconSelfExplanatory
2128                     + "] icon[" + msg.icon + "], sim id: " + slotId);
2129             CatLog.d(LOG_TAG, "Add IdleMode text");
2130             PendingIntent pendingIntent = PendingIntent.getService(mContext, 0,
2131                     new Intent(mContext, StkAppService.class), 0);
2132             createAllChannels();
2133             final Notification.Builder notificationBuilder = new Notification.Builder(
2134                     StkAppService.this, STK_NOTIFICATION_CHANNEL_ID);
2135             if (mStkContext[slotId].mMainCmd != null &&
2136                     mStkContext[slotId].mMainCmd.getMenu() != null) {
2137                 notificationBuilder.setContentTitle(mStkContext[slotId].mMainCmd.getMenu().title);
2138             } else {
2139                 notificationBuilder.setContentTitle("");
2140             }
2141             notificationBuilder
2142                     .setSmallIcon(com.android.internal.R.drawable.stat_notify_sim_toolkit);
2143             notificationBuilder.setContentIntent(pendingIntent);
2144             notificationBuilder.setOngoing(true);
2145             notificationBuilder.setOnlyAlertOnce(true);
2146             // Set text and icon for the status bar and notification body.
2147             if (mStkContext[slotId].mIdleModeTextCmd.hasIconLoadFailed() ||
2148                     !msg.iconSelfExplanatory) {
2149                 notificationBuilder.setContentText(msg.text);
2150                 notificationBuilder.setTicker(msg.text);
2151                 notificationBuilder.setStyle(new Notification.BigTextStyle(notificationBuilder)
2152                         .bigText(msg.text));
2153             }
2154             if (msg.icon != null) {
2155                 notificationBuilder.setLargeIcon(msg.icon);
2156             } else {
2157                 Bitmap bitmapIcon = BitmapFactory.decodeResource(StkAppService.this
2158                     .getResources().getSystem(),
2159                     com.android.internal.R.drawable.stat_notify_sim_toolkit);
2160                 notificationBuilder.setLargeIcon(bitmapIcon);
2161             }
2162             notificationBuilder.setColor(mContext.getResources().getColor(
2163                     com.android.internal.R.color.system_notification_accent_color));
2164             mNotificationManager.notify(getNotificationId(slotId), notificationBuilder.build());
2165             mStkContext[slotId].mIdleModeTextVisible = true;
2166         }
2167     }
2168 
2169     /** Creates the notification channel and registers it with NotificationManager.
2170      * If a channel with the same ID is already registered, NotificationManager will
2171      * ignore this call.
2172      */
createAllChannels()2173     private void createAllChannels() {
2174         NotificationChannel notificationChannel = new NotificationChannel(
2175                 STK_NOTIFICATION_CHANNEL_ID,
2176                 getResources().getString(R.string.stk_channel_name),
2177                 NotificationManager.IMPORTANCE_DEFAULT);
2178 
2179         notificationChannel.enableVibration(true);
2180         notificationChannel.setVibrationPattern(VIBRATION_PATTERN);
2181 
2182         mNotificationManager.createNotificationChannel(notificationChannel);
2183     }
2184 
launchToneDialog(int slotId)2185     private void launchToneDialog(int slotId) {
2186         Intent newIntent = new Intent(this, ToneDialog.class);
2187         String uriString = STK_TONE_URI + slotId;
2188         Uri uriData = Uri.parse(uriString);
2189         //Set unique URI to create a new instance of activity for different slotId.
2190         CatLog.d(LOG_TAG, "launchToneDialog, slotId: " + slotId);
2191         newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2192                 | Intent.FLAG_ACTIVITY_NO_HISTORY
2193                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
2194                 | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId));
2195         newIntent.putExtra("TEXT", mStkContext[slotId].mCurrentCmd.geTextMessage());
2196         newIntent.putExtra("TONE", mStkContext[slotId].mCurrentCmd.getToneSettings());
2197         newIntent.putExtra(SLOT_ID, slotId);
2198         newIntent.setData(uriData);
2199         startActivity(newIntent);
2200     }
2201 
handlePlayTone(int slotId)2202     private void handlePlayTone(int slotId) {
2203         TextMessage toneMsg = mStkContext[slotId].mCurrentCmd.geTextMessage();
2204 
2205         boolean showUser = true;
2206         boolean displayDialog = true;
2207         Resources resource = Resources.getSystem();
2208         try {
2209             displayDialog = !resource.getBoolean(
2210                     com.android.internal.R.bool.config_stkNoAlphaUsrCnf);
2211         } catch (NotFoundException e) {
2212             displayDialog = true;
2213         }
2214 
2215         // As per the spec 3GPP TS 11.14, 6.4.5. Play Tone.
2216         // If there is no alpha identifier tlv present, UE may show the
2217         // user information. 'config_stkNoAlphaUsrCnf' value will decide
2218         // whether to show it or not.
2219         // If alpha identifier tlv is present and its data is null, play only tone
2220         // without showing user any information.
2221         // Alpha Id is Present, but the text data is null.
2222         if ((toneMsg.text != null ) && (toneMsg.text.equals(""))) {
2223             CatLog.d(this, "Alpha identifier data is null, play only tone");
2224             showUser = false;
2225         }
2226         // Alpha Id is not present AND we need to show info to the user.
2227         if (toneMsg.text == null && displayDialog) {
2228             CatLog.d(this, "toneMsg.text " + toneMsg.text
2229                     + " Starting ToneDialog activity with default message.");
2230             toneMsg.text = getResources().getString(R.string.default_tone_dialog_msg);
2231             showUser = true;
2232         }
2233         // Dont show user info, if config setting is true.
2234         if (toneMsg.text == null && !displayDialog) {
2235             CatLog.d(this, "config value stkNoAlphaUsrCnf is true");
2236             showUser = false;
2237         }
2238 
2239         CatLog.d(this, "toneMsg.text: " + toneMsg.text + "showUser: " +showUser +
2240                 "displayDialog: " +displayDialog);
2241         playTone(showUser, slotId);
2242     }
2243 
playTone(boolean showUserInfo, int slotId)2244     private void playTone(boolean showUserInfo, int slotId) {
2245         // Start playing tone and vibration
2246         ToneSettings settings = mStkContext[slotId].mCurrentCmd.getToneSettings();
2247         if (null == settings) {
2248             CatLog.d(this, "null settings, not playing tone.");
2249             return;
2250         }
2251 
2252         mVibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
2253         mTonePlayer = new TonePlayer();
2254         mTonePlayer.play(settings.tone);
2255         int timeout = StkApp.calculateDurationInMilis(settings.duration);
2256         if (timeout == 0) {
2257             timeout = StkApp.TONE_DEFAULT_TIMEOUT;
2258         }
2259 
2260         Message msg = mServiceHandler.obtainMessage();
2261         msg.arg1 = OP_STOP_TONE;
2262         msg.arg2 = slotId;
2263         msg.obj = (Integer)(showUserInfo ? 1 : 0);
2264         msg.what = STOP_TONE_WHAT;
2265         mServiceHandler.sendMessageDelayed(msg, timeout);
2266         if (settings.vibrate) {
2267             mVibrator.vibrate(timeout);
2268         }
2269 
2270         // Start Tone dialog Activity to show user the information.
2271         if (showUserInfo) {
2272             Intent newIntent = new Intent(sInstance, ToneDialog.class);
2273             String uriString = STK_TONE_URI + slotId;
2274             Uri uriData = Uri.parse(uriString);
2275             newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2276                     | Intent.FLAG_ACTIVITY_NO_HISTORY
2277                     | Intent.FLAG_ACTIVITY_SINGLE_TOP
2278                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
2279                     | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId));
2280             newIntent.putExtra("TEXT", mStkContext[slotId].mCurrentCmd.geTextMessage());
2281             newIntent.putExtra(SLOT_ID, slotId);
2282             newIntent.setData(uriData);
2283             startActivity(newIntent);
2284         }
2285     }
2286 
finishToneDialogActivity()2287     private void finishToneDialogActivity() {
2288         Intent finishIntent = new Intent(FINISH_TONE_ACTIVITY_ACTION);
2289         sendBroadcast(finishIntent);
2290     }
2291 
handleStopTone(Message msg, int slotId)2292     private void handleStopTone(Message msg, int slotId) {
2293         int resId = 0;
2294 
2295         // Stop the play tone in following cases:
2296         // 1.OP_STOP_TONE: play tone timer expires.
2297         // 2.STOP_TONE_USER: user pressed the back key.
2298         if (msg.arg1 == OP_STOP_TONE) {
2299             resId = RES_ID_DONE;
2300             // Dismiss Tone dialog, after finishing off playing the tone.
2301             int finishActivity = (Integer) msg.obj;
2302             if (finishActivity == 1) finishToneDialogActivity();
2303         } else if (msg.arg1 == OP_STOP_TONE_USER) {
2304             resId = RES_ID_END_SESSION;
2305         }
2306 
2307         sendResponse(resId, slotId, true);
2308         mServiceHandler.removeMessages(STOP_TONE_WHAT);
2309         if (mTonePlayer != null)  {
2310             mTonePlayer.stop();
2311             mTonePlayer.release();
2312             mTonePlayer = null;
2313         }
2314         if (mVibrator != null) {
2315             mVibrator.cancel();
2316             mVibrator = null;
2317         }
2318     }
2319 
launchOpenChannelDialog(final int slotId)2320     private void launchOpenChannelDialog(final int slotId) {
2321         TextMessage msg = mStkContext[slotId].mCurrentCmd.geTextMessage();
2322         if (msg == null) {
2323             CatLog.d(LOG_TAG, "msg is null, return here");
2324             return;
2325         }
2326 
2327         msg.title = getResources().getString(R.string.stk_dialog_title);
2328         if (msg.text == null) {
2329             msg.text = getResources().getString(R.string.default_open_channel_msg);
2330         }
2331 
2332         final AlertDialog dialog = new AlertDialog.Builder(mContext)
2333                     .setIconAttribute(android.R.attr.alertDialogIcon)
2334                     .setTitle(msg.title)
2335                     .setMessage(msg.text)
2336                     .setCancelable(false)
2337                     .setPositiveButton(getResources().getString(R.string.stk_dialog_accept),
2338                                        new DialogInterface.OnClickListener() {
2339                         public void onClick(DialogInterface dialog, int which) {
2340                             Bundle args = new Bundle();
2341                             args.putInt(RES_ID, RES_ID_CHOICE);
2342                             args.putInt(CHOICE, YES);
2343                             Message message = mServiceHandler.obtainMessage();
2344                             message.arg1 = OP_RESPONSE;
2345                             message.arg2 = slotId;
2346                             message.obj = args;
2347                             mServiceHandler.sendMessage(message);
2348                         }
2349                     })
2350                     .setNegativeButton(getResources().getString(R.string.stk_dialog_reject),
2351                                        new DialogInterface.OnClickListener() {
2352                         public void onClick(DialogInterface dialog, int which) {
2353                             Bundle args = new Bundle();
2354                             args.putInt(RES_ID, RES_ID_CHOICE);
2355                             args.putInt(CHOICE, NO);
2356                             Message message = mServiceHandler.obtainMessage();
2357                             message.arg1 = OP_RESPONSE;
2358                             message.arg2 = slotId;
2359                             message.obj = args;
2360                             mServiceHandler.sendMessage(message);
2361                         }
2362                     })
2363                     .create();
2364 
2365         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
2366         if (!mContext.getResources().getBoolean(
2367                 com.android.internal.R.bool.config_sf_slowBlur)) {
2368             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2369         }
2370 
2371         dialog.show();
2372     }
2373 
launchTransientEventMessage(int slotId)2374     private void launchTransientEventMessage(int slotId) {
2375         TextMessage msg = mStkContext[slotId].mCurrentCmd.geTextMessage();
2376         if (msg == null) {
2377             CatLog.d(LOG_TAG, "msg is null, return here");
2378             return;
2379         }
2380 
2381         msg.title = getResources().getString(R.string.stk_dialog_title);
2382 
2383         final AlertDialog dialog = new AlertDialog.Builder(mContext)
2384                     .setIconAttribute(android.R.attr.alertDialogIcon)
2385                     .setTitle(msg.title)
2386                     .setMessage(msg.text)
2387                     .setCancelable(false)
2388                     .setPositiveButton(getResources().getString(android.R.string.ok),
2389                                        new DialogInterface.OnClickListener() {
2390                         public void onClick(DialogInterface dialog, int which) {
2391                         }
2392                     })
2393                     .create();
2394 
2395         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
2396         if (!mContext.getResources().getBoolean(
2397                 com.android.internal.R.bool.config_sf_slowBlur)) {
2398             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2399         }
2400 
2401         dialog.show();
2402     }
2403 
getNotificationId(int slotId)2404     private int getNotificationId(int slotId) {
2405         int notifyId = STK_NOTIFICATION_ID;
2406         if (slotId >= 0 && slotId < mSimCount) {
2407             notifyId += slotId;
2408         } else {
2409             CatLog.d(LOG_TAG, "invalid slotId: " + slotId);
2410         }
2411         CatLog.d(LOG_TAG, "getNotificationId, slotId: " + slotId + ", notifyId: " + notifyId);
2412         return notifyId;
2413     }
2414 
getItemName(int itemId, int slotId)2415     private String getItemName(int itemId, int slotId) {
2416         Menu menu = mStkContext[slotId].mCurrentCmd.getMenu();
2417         if (menu == null) {
2418             return null;
2419         }
2420         for (Item item : menu.items) {
2421             if (item.id == itemId) {
2422                 return item.text;
2423             }
2424         }
2425         return null;
2426     }
2427 
removeMenu(int slotId)2428     private boolean removeMenu(int slotId) {
2429         try {
2430             if (mStkContext[slotId].mCurrentMenu.items.size() == 1 &&
2431                 mStkContext[slotId].mCurrentMenu.items.get(0) == null) {
2432                 mStkContext[slotId].mSetupMenuState = STATE_NOT_EXIST;
2433                 return true;
2434             }
2435         } catch (NullPointerException e) {
2436             CatLog.d(LOG_TAG, "Unable to get Menu's items size");
2437             mStkContext[slotId].mSetupMenuState = STATE_NOT_EXIST;
2438             return true;
2439         }
2440         mStkContext[slotId].mSetupMenuState = STATE_EXIST;
2441         return false;
2442     }
2443 
getStkContext(int slotId)2444     StkContext getStkContext(int slotId) {
2445         if (slotId >= 0 && slotId < mSimCount) {
2446             return mStkContext[slotId];
2447         } else {
2448             CatLog.d(LOG_TAG, "invalid slotId: " + slotId);
2449             return null;
2450         }
2451     }
2452 
handleAlphaNotify(Bundle args)2453     private void handleAlphaNotify(Bundle args) {
2454         String alphaString = args.getString(AppInterface.ALPHA_STRING);
2455 
2456         CatLog.d(this, "Alpha string received from card: " + alphaString);
2457         Toast toast = Toast.makeText(sInstance, alphaString, Toast.LENGTH_LONG);
2458         toast.setGravity(Gravity.TOP, 0, 0);
2459         toast.show();
2460     }
2461 
isUrlAvailableToLaunchBrowser(BrowserSettings settings)2462     private boolean isUrlAvailableToLaunchBrowser(BrowserSettings settings) {
2463         String url = SystemProperties.get(STK_BROWSER_DEFAULT_URL_SYSPROP, "");
2464         if (url == "" && settings.url == null) {
2465             return false;
2466         }
2467         return true;
2468     }
2469 }
2470