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