| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.stk; |
| |
| import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT; |
| import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT; |
| import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityManager.RunningTaskInfo; |
| import android.app.AlertDialog; |
| import android.app.HomeVisibilityListener; |
| import android.app.KeyguardManager; |
| import android.app.Notification; |
| import android.app.NotificationChannel; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.Service; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Resources; |
| import android.content.res.Resources.NotFoundException; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Parcel; |
| import android.os.PersistableBundle; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.Vibrator; |
| import android.provider.Settings; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyFrameworkInitializer; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import androidx.localbroadcastmanager.content.LocalBroadcastManager; |
| |
| import com.android.internal.telephony.GsmAlphabet; |
| import com.android.internal.telephony.ITelephony; |
| import com.android.internal.telephony.PhoneConfigurationManager; |
| import com.android.internal.telephony.TelephonyIntents; |
| import com.android.internal.telephony.cat.AppInterface; |
| import com.android.internal.telephony.cat.CatCmdMessage; |
| import com.android.internal.telephony.cat.CatCmdMessage.BrowserSettings; |
| import com.android.internal.telephony.cat.CatCmdMessage.SetupEventListSettings; |
| import com.android.internal.telephony.cat.CatLog; |
| import com.android.internal.telephony.cat.CatResponseMessage; |
| import com.android.internal.telephony.cat.CatService; |
| import com.android.internal.telephony.cat.Input; |
| import com.android.internal.telephony.cat.Item; |
| import com.android.internal.telephony.cat.Menu; |
| import com.android.internal.telephony.cat.ResultCode; |
| import com.android.internal.telephony.cat.TextMessage; |
| import com.android.internal.telephony.cat.ToneSettings; |
| import com.android.internal.telephony.uicc.IccRefreshResponse; |
| |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * SIM toolkit application level service. Interacts with Telephopny messages, |
| * application's launch and user input from STK UI elements. |
| * |
| */ |
| public class StkAppService extends Service implements Runnable { |
| |
| // members |
| protected class StkContext { |
| protected CatCmdMessage mMainCmd = null; |
| protected CatCmdMessage mCurrentCmd = null; |
| protected CatCmdMessage mCurrentMenuCmd = null; |
| protected Menu mCurrentMenu = null; |
| protected String lastSelectedItem = null; |
| protected boolean mMenuIsVisible = false; |
| protected boolean mIsInputPending = false; |
| protected boolean mIsMenuPending = false; |
| protected boolean mIsDialogPending = false; |
| protected boolean mNotificationOnKeyguard = false; |
| protected boolean mNoResponseFromUser = false; |
| protected boolean launchBrowser = false; |
| protected BrowserSettings mBrowserSettings = null; |
| protected LinkedList<DelayedCmd> mCmdsQ = null; |
| protected boolean mCmdInProgress = false; |
| protected int mStkServiceState = STATE_UNKNOWN; |
| protected int mMenuState = StkMenuActivity.STATE_INIT; |
| protected int mOpCode = -1; |
| private Activity mActivityInstance = null; |
| private Activity mDialogInstance = null; |
| private Activity mImmediateDialogInstance = null; |
| private int mSlotId = 0; |
| private SetupEventListSettings mSetupEventListSettings = null; |
| private boolean mClearSelectItem = false; |
| private boolean mDisplayTextDlgIsVisibile = false; |
| private CatCmdMessage mCurrentSetupEventCmd = null; |
| private CatCmdMessage mIdleModeTextCmd = null; |
| private boolean mIdleModeTextVisible = false; |
| // Determins whether the current session was initiated by user operation. |
| protected boolean mIsSessionFromUser = false; |
| final synchronized void setPendingActivityInstance(Activity act) { |
| CatLog.d(LOG_TAG, "setPendingActivityInstance act : " + mSlotId + ", " + act); |
| callSetActivityInstMsg(OP_SET_ACT_INST, mSlotId, act); |
| } |
| final synchronized Activity getPendingActivityInstance() { |
| CatLog.d(LOG_TAG, "getPendingActivityInstance act : " + mSlotId + ", " + |
| mActivityInstance); |
| return mActivityInstance; |
| } |
| final synchronized void setPendingDialogInstance(Activity act) { |
| CatLog.d(LOG_TAG, "setPendingDialogInstance act : " + mSlotId + ", " + act); |
| callSetActivityInstMsg(OP_SET_DAL_INST, mSlotId, act); |
| } |
| final synchronized Activity getPendingDialogInstance() { |
| CatLog.d(LOG_TAG, "getPendingDialogInstance act : " + mSlotId + ", " + |
| mDialogInstance); |
| return mDialogInstance; |
| } |
| final synchronized void setImmediateDialogInstance(Activity act) { |
| CatLog.d(LOG_TAG, "setImmediateDialogInstance act : " + mSlotId + ", " + act); |
| callSetActivityInstMsg(OP_SET_IMMED_DAL_INST, mSlotId, act); |
| } |
| final synchronized Activity getImmediateDialogInstance() { |
| CatLog.d(LOG_TAG, "getImmediateDialogInstance act : " + mSlotId + ", " + |
| mImmediateDialogInstance); |
| return mImmediateDialogInstance; |
| } |
| } |
| |
| private volatile Looper mServiceLooper; |
| private volatile ServiceHandler mServiceHandler; |
| private Context mContext = null; |
| private NotificationManager mNotificationManager = null; |
| static StkAppService sInstance = null; |
| private AppInterface[] mStkService = null; |
| private StkContext[] mStkContext = null; |
| private int mSimCount = 0; |
| private HomeVisibilityListener mHomeVisibilityListener = null; |
| private BroadcastReceiver mLocaleChangeReceiver = null; |
| private TonePlayer mTonePlayer = null; |
| private Vibrator mVibrator = null; |
| private BroadcastReceiver mUserActivityReceiver = null; |
| private AlertDialog mAlertDialog = null; |
| |
| // Used for setting FLAG_ACTIVITY_NO_USER_ACTION when |
| // creating an intent. |
| private enum InitiatedByUserAction { |
| yes, // The action was started via a user initiated action |
| unknown, // Not known for sure if user initated the action |
| } |
| |
| // constants |
| static final String OPCODE = "op"; |
| static final String CMD_MSG = "cmd message"; |
| static final String RES_ID = "response id"; |
| static final String MENU_SELECTION = "menu selection"; |
| static final String INPUT = "input"; |
| static final String HELP = "help"; |
| static final String CONFIRMATION = "confirm"; |
| static final String CHOICE = "choice"; |
| static final String SLOT_ID = "SLOT_ID"; |
| static final String STK_CMD = "STK CMD"; |
| static final String STK_DIALOG_URI = "stk://com.android.stk/dialog/"; |
| static final String STK_MENU_URI = "stk://com.android.stk/menu/"; |
| static final String STK_INPUT_URI = "stk://com.android.stk/input/"; |
| static final String STK_TONE_URI = "stk://com.android.stk/tone/"; |
| static final String FINISH_TONE_ACTIVITY_ACTION = |
| "android.intent.action.stk.finish_activity"; |
| |
| // These below constants are used for SETUP_EVENT_LIST |
| static final String SETUP_EVENT_TYPE = "event"; |
| static final String SETUP_EVENT_CAUSE = "cause"; |
| |
| // operations ids for different service functionality. |
| static final int OP_CMD = 1; |
| static final int OP_RESPONSE = 2; |
| static final int OP_LAUNCH_APP = 3; |
| static final int OP_END_SESSION = 4; |
| static final int OP_BOOT_COMPLETED = 5; |
| private static final int OP_DELAYED_MSG = 6; |
| static final int OP_CARD_STATUS_CHANGED = 7; |
| static final int OP_SET_ACT_INST = 8; |
| static final int OP_SET_DAL_INST = 9; |
| static final int OP_LOCALE_CHANGED = 10; |
| static final int OP_ALPHA_NOTIFY = 11; |
| static final int OP_IDLE_SCREEN = 12; |
| static final int OP_SET_IMMED_DAL_INST = 13; |
| static final int OP_HOME_KEY_PRESSED = 14; |
| |
| //Invalid SetupEvent |
| static final int INVALID_SETUP_EVENT = 0xFF; |
| |
| // Message id to signal stop tone due to play tone timeout. |
| private static final int OP_STOP_TONE = 16; |
| |
| // Message id to signal stop tone on user keyback. |
| static final int OP_STOP_TONE_USER = 17; |
| |
| // Message id to send user activity event to card. |
| private static final int OP_USER_ACTIVITY = 20; |
| |
| // Message id that multi-SIM config has changed (ss <-> ds). |
| private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 21; |
| |
| // Response ids |
| static final int RES_ID_MENU_SELECTION = 11; |
| static final int RES_ID_INPUT = 12; |
| static final int RES_ID_CONFIRM = 13; |
| static final int RES_ID_DONE = 14; |
| static final int RES_ID_CHOICE = 15; |
| |
| static final int RES_ID_TIMEOUT = 20; |
| static final int RES_ID_BACKWARD = 21; |
| static final int RES_ID_END_SESSION = 22; |
| static final int RES_ID_EXIT = 23; |
| static final int RES_ID_ERROR = 24; |
| |
| static final int YES = 1; |
| static final int NO = 0; |
| |
| static final int STATE_UNKNOWN = -1; |
| static final int STATE_NOT_EXIST = 0; |
| static final int STATE_EXIST = 1; |
| |
| private static final Integer PLAY_TONE_ONLY = 0; |
| private static final Integer PLAY_TONE_WITH_DIALOG = 1; |
| |
| private static final String PACKAGE_NAME = "com.android.stk"; |
| private static final String STK_MENU_ACTIVITY_NAME = PACKAGE_NAME + ".StkMenuActivity"; |
| private static final String STK_INPUT_ACTIVITY_NAME = PACKAGE_NAME + ".StkInputActivity"; |
| private static final String STK_DIALOG_ACTIVITY_NAME = PACKAGE_NAME + ".StkDialogActivity"; |
| // Notification id used to display Idle Mode text in NotificationManager. |
| private static final int STK_NOTIFICATION_ID = 333; |
| // Notification channel containing all mobile service messages notifications. |
| private static final String STK_NOTIFICATION_CHANNEL_ID = "mobileServiceMessages"; |
| |
| private static final String LOG_TAG = StkAppService.class.getSimpleName(); |
| |
| static final String SESSION_ENDED = "session_ended"; |
| |
| // Inner class used for queuing telephony messages (proactive commands, |
| // session end) while the service is busy processing a previous message. |
| private class DelayedCmd { |
| // members |
| int id; |
| CatCmdMessage msg; |
| int slotId; |
| |
| DelayedCmd(int id, CatCmdMessage msg, int slotId) { |
| this.id = id; |
| this.msg = msg; |
| this.slotId = slotId; |
| } |
| } |
| |
| // system property to set the STK specific default url for launch browser proactive cmds |
| private static final String STK_BROWSER_DEFAULT_URL_SYSPROP = "persist.radio.stk.default_url"; |
| |
| private static final int NOTIFICATION_ON_KEYGUARD = 1; |
| private static final long[] VIBRATION_PATTERN = new long[] { 0, 350, 250, 350 }; |
| private BroadcastReceiver mUserPresentReceiver = null; |
| |
| // The reason based on Intent.ACTION_CLOSE_SYSTEM_DIALOGS. |
| private static final String SYSTEM_DIALOG_REASON_KEY = "reason"; |
| private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; |
| private static final String SYSTEM_DIALOG_REASON_RECENTAPPS_KEY = "recentapps"; |
| private BroadcastReceiver mHomeKeyEventReceiver = null; |
| private static final int NOTIFICATION_PENDING_INTENT_REQUEST_CODE = 0; |
| |
| @Override |
| public void onCreate() { |
| CatLog.d(LOG_TAG, "onCreate()+"); |
| // Initialize members |
| int i = 0; |
| mContext = getBaseContext(); |
| mSimCount = TelephonyManager.from(mContext).getActiveModemCount(); |
| int maxSimCount = TelephonyManager.from(mContext).getSupportedModemCount(); |
| CatLog.d(LOG_TAG, "simCount: " + mSimCount); |
| mStkService = new AppInterface[maxSimCount]; |
| mStkContext = new StkContext[maxSimCount]; |
| |
| for (i = 0; i < mSimCount; i++) { |
| CatLog.d(LOG_TAG, "slotId: " + i); |
| mStkService[i] = CatService.getInstance(i); |
| mStkContext[i] = new StkContext(); |
| mStkContext[i].mSlotId = i; |
| mStkContext[i].mCmdsQ = new LinkedList<DelayedCmd>(); |
| } |
| |
| Thread serviceThread = new Thread(null, this, "Stk App Service"); |
| serviceThread.start(); |
| mNotificationManager = (NotificationManager) mContext |
| .getSystemService(Context.NOTIFICATION_SERVICE); |
| sInstance = this; |
| } |
| |
| @Override |
| public void onStart(Intent intent, int startId) { |
| if (intent == null) { |
| CatLog.d(LOG_TAG, "StkAppService onStart intent is null so return"); |
| return; |
| } |
| |
| Bundle args = intent.getExtras(); |
| if (args == null) { |
| CatLog.d(LOG_TAG, "StkAppService onStart args is null so return"); |
| return; |
| } |
| |
| int op = args.getInt(OPCODE); |
| int slotId = 0; |
| int i = 0; |
| if (op != OP_BOOT_COMPLETED) { |
| slotId = args.getInt(SLOT_ID); |
| } |
| CatLog.d(LOG_TAG, "onStart sim id: " + slotId + ", op: " + op + ", *****"); |
| if ((slotId >= 0 && slotId < mSimCount) && mStkService[slotId] == null) { |
| mStkService[slotId] = CatService.getInstance(slotId); |
| if (mStkService[slotId] == null) { |
| CatLog.d(LOG_TAG, "mStkService is: " + mStkContext[slotId].mStkServiceState); |
| mStkContext[slotId].mStkServiceState = STATE_NOT_EXIST; |
| //Check other StkService state. |
| //If all StkServices are not available, stop itself and uninstall apk. |
| for (i = 0; i < mSimCount; i++) { |
| if (i != slotId |
| && (mStkService[i] != null) |
| && (mStkContext[i].mStkServiceState == STATE_UNKNOWN |
| || mStkContext[i].mStkServiceState == STATE_EXIST)) { |
| break; |
| } |
| } |
| } else { |
| mStkContext[slotId].mStkServiceState = STATE_EXIST; |
| } |
| if (i == mSimCount) { |
| stopSelf(); |
| StkAppInstaller.uninstall(this); |
| return; |
| } |
| } |
| |
| waitForLooper(); |
| |
| Message msg = mServiceHandler.obtainMessage(op, 0, slotId); |
| switch (op) { |
| case OP_CMD: |
| msg.obj = args.getParcelable(CMD_MSG); |
| break; |
| case OP_RESPONSE: |
| case OP_CARD_STATUS_CHANGED: |
| case OP_LOCALE_CHANGED: |
| case OP_ALPHA_NOTIFY: |
| case OP_IDLE_SCREEN: |
| case OP_STOP_TONE_USER: |
| msg.obj = args; |
| /* falls through */ |
| case OP_LAUNCH_APP: |
| case OP_END_SESSION: |
| case OP_BOOT_COMPLETED: |
| break; |
| default: |
| return; |
| } |
| mServiceHandler.sendMessage(msg); |
| } |
| |
| @Override |
| public void onDestroy() { |
| CatLog.d(LOG_TAG, "onDestroy()"); |
| unregisterUserActivityReceiver(); |
| unregisterHomeVisibilityObserver(); |
| unregisterLocaleChangeReceiver(); |
| unregisterHomeKeyEventReceiver(); |
| // close the AlertDialog if any is showing upon sim remove etc cases |
| if (mAlertDialog != null && mAlertDialog.isShowing()) { |
| mAlertDialog.dismiss(); |
| mAlertDialog = null; |
| } |
| sInstance = null; |
| waitForLooper(); |
| PhoneConfigurationManager.unregisterForMultiSimConfigChange(mServiceHandler); |
| mServiceLooper.quit(); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return null; |
| } |
| |
| public void run() { |
| Looper.prepare(); |
| |
| mServiceLooper = Looper.myLooper(); |
| mServiceHandler = new ServiceHandler(); |
| |
| PhoneConfigurationManager.registerForMultiSimConfigChange(mServiceHandler, |
| EVENT_MULTI_SIM_CONFIG_CHANGED, null); |
| |
| Looper.loop(); |
| } |
| |
| /* |
| * Package api used by StkMenuActivity to indicate if its on the foreground. |
| */ |
| synchronized void indicateMenuVisibility(boolean visibility, int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| mStkContext[slotId].mMenuIsVisible = visibility; |
| } |
| } |
| |
| /* |
| * Package api used by StkDialogActivity to indicate if its on the foreground. |
| */ |
| synchronized void setDisplayTextDlgVisibility(boolean visibility, int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| mStkContext[slotId].mDisplayTextDlgIsVisibile = visibility; |
| } |
| } |
| |
| synchronized boolean isInputPending(int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| CatLog.d(LOG_TAG, "isInputFinishBySrv: " + mStkContext[slotId].mIsInputPending); |
| return mStkContext[slotId].mIsInputPending; |
| } |
| return false; |
| } |
| |
| synchronized boolean isMenuPending(int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| CatLog.d(LOG_TAG, "isMenuPending: " + mStkContext[slotId].mIsMenuPending); |
| return mStkContext[slotId].mIsMenuPending; |
| } |
| return false; |
| } |
| |
| synchronized boolean isDialogPending(int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| CatLog.d(LOG_TAG, "isDialogPending: " + mStkContext[slotId].mIsDialogPending); |
| return mStkContext[slotId].mIsDialogPending; |
| } |
| return false; |
| } |
| |
| synchronized boolean isMainMenuAvailable(int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| // The main menu can handle the next user operation if the previous session finished. |
| return (mStkContext[slotId].lastSelectedItem == null) ? true : false; |
| } |
| return false; |
| } |
| |
| /* |
| * Package api used by StkMenuActivity to get its Menu parameter. |
| */ |
| synchronized Menu getMenu(int slotId) { |
| CatLog.d(LOG_TAG, "StkAppService, getMenu, sim id: " + slotId); |
| if (slotId >=0 && slotId < mSimCount) { |
| return mStkContext[slotId].mCurrentMenu; |
| } else { |
| return null; |
| } |
| } |
| |
| /* |
| * Package api used by StkMenuActivity to get its Main Menu parameter. |
| */ |
| synchronized Menu getMainMenu(int slotId) { |
| CatLog.d(LOG_TAG, "StkAppService, getMainMenu, sim id: " + slotId); |
| if (slotId >=0 && slotId < mSimCount && (mStkContext[slotId].mMainCmd != null)) { |
| Menu menu = mStkContext[slotId].mMainCmd.getMenu(); |
| if (menu != null) { |
| // If alpha identifier or icon identifier with the self-explanatory qualifier is |
| // specified in SET-UP MENU command, it should be more prioritized than preset ones. |
| if (menu.title == null |
| && (menu.titleIcon == null || !menu.titleIconSelfExplanatory)) { |
| StkMenuConfig config = StkMenuConfig.getInstance(getApplicationContext()); |
| String label = config.getLabel(slotId); |
| Bitmap icon = config.getIcon(slotId); |
| if (label != null || icon != null) { |
| Parcel parcel = Parcel.obtain(); |
| menu.writeToParcel(parcel, 0); |
| parcel.setDataPosition(0); |
| menu = Menu.CREATOR.createFromParcel(parcel); |
| parcel.recycle(); |
| menu.title = label; |
| menu.titleIcon = icon; |
| menu.titleIconSelfExplanatory = false; |
| } |
| } |
| } |
| return menu; |
| } else { |
| return null; |
| } |
| } |
| |
| /* |
| * Package api used by UI Activities and Dialogs to communicate directly |
| * with the service to deliver state information and parameters. |
| */ |
| static StkAppService getInstance() { |
| return sInstance; |
| } |
| |
| private void waitForLooper() { |
| while (mServiceHandler == null) { |
| synchronized (this) { |
| try { |
| wait(100); |
| } catch (InterruptedException e) { |
| } |
| } |
| } |
| } |
| |
| private final class ServiceHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| if(null == msg) { |
| CatLog.d(LOG_TAG, "ServiceHandler handleMessage msg is null"); |
| return; |
| } |
| int opcode = msg.what; |
| int slotId = msg.arg2; |
| |
| CatLog.d(LOG_TAG, "handleMessage opcode[" + opcode + "], sim id[" + slotId + "]"); |
| if (opcode == OP_CMD && msg.obj != null && |
| ((CatCmdMessage)msg.obj).getCmdType()!= null) { |
| CatLog.d(LOG_TAG, "cmdName[" + ((CatCmdMessage)msg.obj).getCmdType().name() + "]"); |
| } |
| if (slotId >= mStkContext.length || mStkContext[slotId] == null) { |
| CatLog.d(LOG_TAG, "invalid slotId " + slotId); |
| return; |
| } |
| |
| mStkContext[slotId].mOpCode = opcode; |
| switch (opcode) { |
| case OP_LAUNCH_APP: |
| if (mStkContext[slotId].mMainCmd == null) { |
| CatLog.d(LOG_TAG, "mMainCmd is null"); |
| // nothing todo when no SET UP MENU command didn't arrive. |
| return; |
| } |
| CatLog.d(LOG_TAG, "handleMessage OP_LAUNCH_APP - mCmdInProgress[" + |
| mStkContext[slotId].mCmdInProgress + "]"); |
| |
| //If there is a pending activity for the slot id, |
| //just finish it and create a new one to handle the pending command. |
| cleanUpInstanceStackBySlot(slotId); |
| |
| CatLog.d(LOG_TAG, "Current cmd type: " + |
| mStkContext[slotId].mCurrentCmd.getCmdType()); |
| //Restore the last command from stack by slot id. |
| restoreInstanceFromStackBySlot(slotId); |
| break; |
| case OP_CMD: |
| CatLog.d(LOG_TAG, "[OP_CMD]"); |
| CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj; |
| // There are two types of commands: |
| // 1. Interactive - user's response is required. |
| // 2. Informative - display a message, no interaction with the user. |
| // |
| // Informative commands can be handled immediately without any delay. |
| // Interactive commands can't override each other. So if a command |
| // is already in progress, we need to queue the next command until |
| // the user has responded or a timeout expired. |
| if (!isCmdInteractive(cmdMsg)) { |
| handleCmd(cmdMsg, slotId); |
| } else { |
| if (!mStkContext[slotId].mCmdInProgress) { |
| mStkContext[slotId].mCmdInProgress = true; |
| handleCmd((CatCmdMessage) msg.obj, slotId); |
| } else { |
| CatLog.d(LOG_TAG, "[Interactive][in progress]"); |
| mStkContext[slotId].mCmdsQ.addLast(new DelayedCmd(OP_CMD, |
| (CatCmdMessage) msg.obj, slotId)); |
| } |
| } |
| break; |
| case OP_RESPONSE: |
| handleCmdResponse((Bundle) msg.obj, slotId); |
| // call delayed commands if needed. |
| if (mStkContext[slotId].mCmdsQ.size() != 0) { |
| callDelayedMsg(slotId); |
| } else { |
| mStkContext[slotId].mCmdInProgress = false; |
| } |
| break; |
| case OP_END_SESSION: |
| if (!mStkContext[slotId].mCmdInProgress) { |
| mStkContext[slotId].mCmdInProgress = true; |
| handleSessionEnd(slotId); |
| } else { |
| mStkContext[slotId].mCmdsQ.addLast( |
| new DelayedCmd(OP_END_SESSION, null, slotId)); |
| } |
| break; |
| case OP_BOOT_COMPLETED: |
| CatLog.d(LOG_TAG, " OP_BOOT_COMPLETED"); |
| uninstallIfUnnecessary(); |
| break; |
| case OP_DELAYED_MSG: |
| handleDelayedCmd(slotId); |
| break; |
| case OP_CARD_STATUS_CHANGED: |
| CatLog.d(LOG_TAG, "Card/Icc Status change received"); |
| handleCardStatusChangeAndIccRefresh((Bundle) msg.obj, slotId); |
| break; |
| case OP_SET_ACT_INST: |
| Activity act = (Activity) msg.obj; |
| if (mStkContext[slotId].mActivityInstance != act) { |
| CatLog.d(LOG_TAG, "Set pending activity instance - " + act); |
| Activity previous = mStkContext[slotId].mActivityInstance; |
| mStkContext[slotId].mActivityInstance = act; |
| // Finish the previous one if it was replaced with new one |
| // but it has not been finished yet somehow. |
| if (act != null && previous != null && !previous.isDestroyed() |
| && !previous.isFinishing()) { |
| CatLog.d(LOG_TAG, "Finish the previous pending activity - " + previous); |
| previous.finish(); |
| } |
| } |
| // Clear pending dialog instance if it has not been cleared yet. |
| Activity dialog = mStkContext[slotId].getPendingDialogInstance(); |
| if (dialog != null && (dialog.isDestroyed() || dialog.isFinishing())) { |
| CatLog.d(LOG_TAG, "Clear pending dialog instance - " + dialog); |
| mStkContext[slotId].mDialogInstance = null; |
| } |
| break; |
| case OP_SET_DAL_INST: |
| Activity dal = (Activity) msg.obj; |
| if (mStkContext[slotId].mDialogInstance != dal) { |
| CatLog.d(LOG_TAG, "Set pending dialog instance - " + dal); |
| mStkContext[slotId].mDialogInstance = dal; |
| } |
| break; |
| case OP_SET_IMMED_DAL_INST: |
| Activity immedDal = (Activity) msg.obj; |
| CatLog.d(LOG_TAG, "Set dialog instance for immediate response. " + immedDal); |
| mStkContext[slotId].mImmediateDialogInstance = immedDal; |
| break; |
| case OP_LOCALE_CHANGED: |
| CatLog.d(LOG_TAG, "Locale Changed"); |
| for (int slot = 0; slot < mSimCount; slot++) { |
| checkForSetupEvent(LANGUAGE_SELECTION_EVENT, (Bundle) msg.obj, slot); |
| } |
| // rename all registered notification channels on locale change |
| createAllChannels(); |
| break; |
| case OP_ALPHA_NOTIFY: |
| handleAlphaNotify((Bundle) msg.obj); |
| break; |
| case OP_IDLE_SCREEN: |
| for (int slot = 0; slot < mSimCount; slot++) { |
| if (mStkContext[slot] != null) { |
| handleIdleScreen(slot); |
| } |
| } |
| break; |
| case OP_STOP_TONE_USER: |
| case OP_STOP_TONE: |
| CatLog.d(LOG_TAG, "Stop tone"); |
| handleStopTone(msg, slotId); |
| break; |
| case OP_USER_ACTIVITY: |
| for (int slot = 0; slot < mSimCount; slot++) { |
| checkForSetupEvent(USER_ACTIVITY_EVENT, null, slot); |
| } |
| break; |
| case EVENT_MULTI_SIM_CONFIG_CHANGED: |
| handleMultiSimConfigChanged(); |
| break; |
| case OP_HOME_KEY_PRESSED: |
| CatLog.d(LOG_TAG, "Process the home key pressed event"); |
| for (int slot = 0; slot < mSimCount; slot++) { |
| if (mStkContext[slot] != null) { |
| handleHomeKeyPressed(slot); |
| } |
| } |
| break; |
| } |
| } |
| |
| private void handleCardStatusChangeAndIccRefresh(Bundle args, int slotId) { |
| boolean cardStatus = args.getBoolean(AppInterface.CARD_STATUS); |
| |
| CatLog.d(LOG_TAG, "CardStatus: " + cardStatus); |
| if (cardStatus == false) { |
| CatLog.d(LOG_TAG, "CARD is ABSENT"); |
| // Uninstall STKAPP, Clear Idle text, Stop StkAppService |
| cancelIdleText(slotId); |
| mStkContext[slotId].mCurrentMenu = null; |
| mStkContext[slotId].mMainCmd = null; |
| mStkService[slotId] = null; |
| // Stop the tone currently being played if the relevant SIM is removed or disabled. |
| if (mStkContext[slotId].mCurrentCmd != null |
| && mStkContext[slotId].mCurrentCmd.getCmdType().value() |
| == AppInterface.CommandType.PLAY_TONE.value()) { |
| terminateTone(slotId); |
| } |
| if (!uninstallIfUnnecessary()) { |
| addToMenuSystemOrUpdateLabel(); |
| } |
| if (isAllOtherCardsAbsent(slotId)) { |
| CatLog.d(LOG_TAG, "All CARDs are ABSENT"); |
| stopSelf(); |
| } |
| } else { |
| IccRefreshResponse state = new IccRefreshResponse(); |
| state.refreshResult = args.getInt(AppInterface.REFRESH_RESULT); |
| |
| CatLog.d(LOG_TAG, "Icc Refresh Result: "+ state.refreshResult); |
| if ((state.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) || |
| (state.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET)) { |
| // Clear Idle Text |
| cancelIdleText(slotId); |
| } |
| } |
| } |
| } |
| |
| private synchronized void handleMultiSimConfigChanged() { |
| int oldSimCount = mSimCount; |
| mSimCount = TelephonyManager.from(mContext).getActiveModemCount(); |
| for (int i = oldSimCount; i < mSimCount; i++) { |
| CatLog.d(LOG_TAG, "slotId: " + i); |
| mStkService[i] = CatService.getInstance(i); |
| mStkContext[i] = new StkContext(); |
| mStkContext[i].mSlotId = i; |
| mStkContext[i].mCmdsQ = new LinkedList<DelayedCmd>(); |
| } |
| |
| for (int i = mSimCount; i < oldSimCount; i++) { |
| CatLog.d(LOG_TAG, "slotId: " + i); |
| if (mStkService[i] != null) { |
| mStkService[i].dispose(); |
| mStkService[i] = null; |
| } |
| mStkContext[i] = null; |
| } |
| } |
| |
| /* |
| * Check if all SIMs are absent except the id of slot equals "slotId". |
| */ |
| private boolean isAllOtherCardsAbsent(int slotId) { |
| TelephonyManager mTm = (TelephonyManager) mContext.getSystemService( |
| Context.TELEPHONY_SERVICE); |
| int i = 0; |
| |
| for (i = 0; i < mSimCount; i++) { |
| if (i != slotId && mTm.hasIccCard(i)) { |
| break; |
| } |
| } |
| if (i == mSimCount) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /* package */ boolean isScreenIdle() { |
| ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); |
| List<RunningTaskInfo> tasks = am.getRunningTasks(1); |
| if (tasks == null || tasks.isEmpty()) { |
| return false; |
| } |
| |
| String top = tasks.get(0).topActivity.getPackageName(); |
| if (top == null) { |
| return false; |
| } |
| |
| // We can assume that the screen is idle if the home application is in the foreground. |
| final Intent intent = new Intent(Intent.ACTION_MAIN, null); |
| intent.addCategory(Intent.CATEGORY_HOME); |
| |
| ResolveInfo info = getPackageManager().resolveActivity(intent, |
| PackageManager.MATCH_DEFAULT_ONLY); |
| if (info != null) { |
| if (top.equals(info.activityInfo.packageName)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private synchronized void startToObserveHomeKeyEvent(int slotId) { |
| if (mStkContext[slotId].mIsSessionFromUser || mHomeKeyEventReceiver != null) { |
| return; |
| } |
| mHomeKeyEventReceiver = new BroadcastReceiver() { |
| @Override public void onReceive(Context context, Intent intent) { |
| final String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); |
| // gesture-based launchers may interpret swipe-up as "recent apps" instead of |
| // "home" so we accept both here |
| if (SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason) |
| || SYSTEM_DIALOG_REASON_RECENTAPPS_KEY.equals(reason)) { |
| Message message = mServiceHandler.obtainMessage(OP_HOME_KEY_PRESSED); |
| mServiceHandler.sendMessage(message); |
| } |
| } |
| }; |
| CatLog.d(LOG_TAG, "Started to observe home key event"); |
| registerReceiver(mHomeKeyEventReceiver, |
| new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), Context.RECEIVER_EXPORTED); |
| } |
| |
| private synchronized void unregisterHomeKeyEventReceiver() { |
| if (mHomeKeyEventReceiver != null) { |
| CatLog.d(LOG_TAG, "Stopped to observe home key event"); |
| unregisterReceiver(mHomeKeyEventReceiver); |
| mHomeKeyEventReceiver = null; |
| } |
| if (mServiceHandler != null) { |
| mServiceHandler.removeMessages(OP_HOME_KEY_PRESSED); |
| } |
| } |
| |
| private void handleHomeKeyPressed(int slotId) { |
| // It might be hard for user to recognize that the dialog or screens belong to SIM Toolkit |
| // application if the current session was not initiated by user but by the SIM card, |
| // so it is recommended to send TERMINAL RESPONSE if user press the home key. |
| if (!mStkContext[slotId].mIsSessionFromUser) { |
| Activity dialog = mStkContext[slotId].getPendingDialogInstance(); |
| Activity activity = mStkContext[slotId].getPendingActivityInstance(); |
| if (dialog != null) { |
| dialog.finish(); |
| mStkContext[slotId].mDialogInstance = null; |
| } else if (activity != null) { |
| activity.finish(); |
| mStkContext[slotId].mActivityInstance = null; |
| } |
| } |
| } |
| |
| private void handleIdleScreen(int slotId) { |
| // If the idle screen event is present in the list need to send the |
| // response to SIM. |
| CatLog.d(LOG_TAG, "Need to send IDLE SCREEN Available event to SIM"); |
| checkForSetupEvent(IDLE_SCREEN_AVAILABLE_EVENT, null, slotId); |
| |
| if (mStkContext[slotId].mIdleModeTextCmd != null |
| && !mStkContext[slotId].mIdleModeTextVisible) { |
| launchIdleText(slotId); |
| } |
| } |
| |
| private void sendScreenBusyResponse(int slotId) { |
| if (mStkContext[slotId].mCurrentCmd == null) { |
| return; |
| } |
| CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentCmd); |
| CatLog.d(LOG_TAG, "SCREEN_BUSY"); |
| resMsg.setResultCode(ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS); |
| mStkService[slotId].onCmdResponse(resMsg); |
| if (mStkContext[slotId].mCmdsQ.size() != 0) { |
| callDelayedMsg(slotId); |
| } else { |
| mStkContext[slotId].mCmdInProgress = false; |
| } |
| } |
| |
| /** |
| * Sends TERMINAL RESPONSE or ENVELOPE |
| * |
| * @param args detailed parameters of the response |
| * @param slotId slot identifier |
| */ |
| public void sendResponse(Bundle args, int slotId) { |
| Message msg = mServiceHandler.obtainMessage(OP_RESPONSE, 0, slotId, args); |
| mServiceHandler.sendMessage(msg); |
| } |
| |
| private void sendResponse(int resId, int slotId, boolean confirm) { |
| Bundle args = new Bundle(); |
| args.putInt(StkAppService.RES_ID, resId); |
| args.putBoolean(StkAppService.CONFIRMATION, confirm); |
| sendResponse(args, slotId); |
| } |
| |
| private void terminateTone(int slotId) { |
| Message msg = new Message(); |
| msg.what = OP_STOP_TONE; |
| msg.obj = mServiceHandler.hasMessages(OP_STOP_TONE, PLAY_TONE_WITH_DIALOG) |
| ? PLAY_TONE_WITH_DIALOG : PLAY_TONE_ONLY; |
| handleStopTone(msg, slotId); |
| } |
| |
| private boolean isCmdInteractive(CatCmdMessage cmd) { |
| switch (cmd.getCmdType()) { |
| case SEND_DTMF: |
| case SEND_SMS: |
| case REFRESH: |
| case RUN_AT: |
| case SEND_SS: |
| case SEND_USSD: |
| case SET_UP_IDLE_MODE_TEXT: |
| case SET_UP_MENU: |
| case CLOSE_CHANNEL: |
| case RECEIVE_DATA: |
| case SEND_DATA: |
| case SET_UP_EVENT_LIST: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private void handleDelayedCmd(int slotId) { |
| CatLog.d(LOG_TAG, "handleDelayedCmd, slotId: " + slotId); |
| if (mStkContext[slotId].mCmdsQ.size() != 0) { |
| DelayedCmd cmd = mStkContext[slotId].mCmdsQ.poll(); |
| if (cmd != null) { |
| CatLog.d(LOG_TAG, "handleDelayedCmd - queue size: " + |
| mStkContext[slotId].mCmdsQ.size() + |
| " id: " + cmd.id + "sim id: " + cmd.slotId); |
| switch (cmd.id) { |
| case OP_CMD: |
| handleCmd(cmd.msg, cmd.slotId); |
| break; |
| case OP_END_SESSION: |
| handleSessionEnd(cmd.slotId); |
| break; |
| } |
| } |
| } |
| } |
| |
| private void callDelayedMsg(int slotId) { |
| Message msg = mServiceHandler.obtainMessage(OP_DELAYED_MSG, 0, slotId); |
| mServiceHandler.sendMessage(msg); |
| } |
| |
| private void callSetActivityInstMsg(int opcode, int slotId, Object obj) { |
| Message msg = mServiceHandler.obtainMessage(opcode, 0, slotId, obj); |
| mServiceHandler.sendMessage(msg); |
| } |
| |
| private void handleSessionEnd(int slotId) { |
| // We should finish all pending activity if receiving END SESSION command. |
| cleanUpInstanceStackBySlot(slotId); |
| |
| mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd; |
| CatLog.d(LOG_TAG, "[handleSessionEnd] - mCurrentCmd changed to mMainCmd!."); |
| mStkContext[slotId].mCurrentMenuCmd = mStkContext[slotId].mMainCmd; |
| CatLog.d(LOG_TAG, "slotId: " + slotId + ", mMenuState: " + |
| mStkContext[slotId].mMenuState); |
| |
| mStkContext[slotId].mIsInputPending = false; |
| mStkContext[slotId].mIsMenuPending = false; |
| mStkContext[slotId].mIsDialogPending = false; |
| mStkContext[slotId].mNoResponseFromUser = false; |
| |
| if (mStkContext[slotId].mMainCmd == null) { |
| CatLog.d(LOG_TAG, "[handleSessionEnd][mMainCmd is null!]"); |
| } |
| mStkContext[slotId].lastSelectedItem = null; |
| mStkContext[slotId].mIsSessionFromUser = false; |
| // In case of SET UP MENU command which removed the app, don't |
| // update the current menu member. |
| if (mStkContext[slotId].mCurrentMenu != null && mStkContext[slotId].mMainCmd != null) { |
| mStkContext[slotId].mCurrentMenu = mStkContext[slotId].mMainCmd.getMenu(); |
| } |
| CatLog.d(LOG_TAG, "[handleSessionEnd][mMenuState]" + mStkContext[slotId].mMenuIsVisible); |
| |
| if (StkMenuActivity.STATE_SECONDARY == mStkContext[slotId].mMenuState) { |
| mStkContext[slotId].mMenuState = StkMenuActivity.STATE_MAIN; |
| } |
| |
| // Send a local broadcast as a notice that this service handled the session end event. |
| Intent intent = new Intent(SESSION_ENDED); |
| intent.putExtra(SLOT_ID, slotId); |
| LocalBroadcastManager.getInstance(this).sendBroadcast(intent); |
| |
| if (mStkContext[slotId].mCmdsQ.size() != 0) { |
| callDelayedMsg(slotId); |
| } else { |
| mStkContext[slotId].mCmdInProgress = false; |
| } |
| // In case a launch browser command was just confirmed, launch that url. |
| if (mStkContext[slotId].launchBrowser) { |
| mStkContext[slotId].launchBrowser = false; |
| launchBrowser(mStkContext[slotId].mBrowserSettings); |
| } |
| } |
| |
| // returns true if any Stk related activity already has focus on the screen |
| boolean isTopOfStack() { |
| ActivityManager mActivityManager = (ActivityManager) mContext |
| .getSystemService(ACTIVITY_SERVICE); |
| String currentPackageName = null; |
| List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1); |
| if (tasks == null || tasks.get(0).topActivity == null) { |
| return false; |
| } |
| currentPackageName = tasks.get(0).topActivity.getPackageName(); |
| if (null != currentPackageName) { |
| return currentPackageName.equals(PACKAGE_NAME); |
| } |
| return false; |
| } |
| |
| /** |
| * Get the boolean config from carrier config manager. |
| * |
| * @param context the context to get carrier service |
| * @param key config key defined in CarrierConfigManager |
| * @param slotId slot ID. |
| * @return boolean value of corresponding key. |
| */ |
| /* package */ static boolean getBooleanCarrierConfig(Context context, String key, int slotId) { |
| CarrierConfigManager ccm = (CarrierConfigManager) context.getSystemService( |
| Context.CARRIER_CONFIG_SERVICE); |
| SubscriptionManager sm = (SubscriptionManager) context.getSystemService( |
| Context.TELEPHONY_SUBSCRIPTION_SERVICE); |
| PersistableBundle b = null; |
| if (ccm != null && sm != null) { |
| SubscriptionInfo info = sm.getActiveSubscriptionInfoForSimSlotIndex(slotId); |
| if (info != null) { |
| b = ccm.getConfigForSubId(info.getSubscriptionId()); |
| } |
| } |
| if (b != null) { |
| return b.getBoolean(key); |
| } |
| // Return static default defined in CarrierConfigManager. |
| return CarrierConfigManager.getDefaultConfig().getBoolean(key); |
| } |
| |
| private boolean getBooleanCarrierConfig(String key, int slotId) { |
| return getBooleanCarrierConfig(this, key, slotId); |
| } |
| |
| private void handleCmd(CatCmdMessage cmdMsg, int slotId) { |
| |
| if (cmdMsg == null) { |
| return; |
| } |
| // save local reference for state tracking. |
| mStkContext[slotId].mCurrentCmd = cmdMsg; |
| boolean waitForUsersResponse = true; |
| |
| mStkContext[slotId].mIsInputPending = false; |
| mStkContext[slotId].mIsMenuPending = false; |
| mStkContext[slotId].mIsDialogPending = false; |
| |
| CatLog.d(LOG_TAG,"[handleCmd]" + cmdMsg.getCmdType().name()); |
| switch (cmdMsg.getCmdType()) { |
| case DISPLAY_TEXT: |
| TextMessage msg = cmdMsg.geTextMessage(); |
| waitForUsersResponse = msg.responseNeeded; |
| //If we receive a low priority Display Text and the device is |
| // not displaying any STK related activity and the screen is not idle |
| // ( that is, device is in an interactive state), then send a screen busy |
| // terminal response. Otherwise display the message. The existing |
| // displayed message shall be updated with the new display text |
| // proactive command (Refer to ETSI TS 102 384 section 27.22.4.1.4.4.2). |
| if (!(msg.isHighPriority || mStkContext[slotId].mMenuIsVisible |
| || mStkContext[slotId].mDisplayTextDlgIsVisibile || isTopOfStack())) { |
| if(!isScreenIdle()) { |
| CatLog.d(LOG_TAG, "Screen is not idle"); |
| sendScreenBusyResponse(slotId); |
| } else { |
| launchTextDialog(slotId); |
| } |
| } else { |
| launchTextDialog(slotId); |
| } |
| break; |
| case SELECT_ITEM: |
| CatLog.d(LOG_TAG, "SELECT_ITEM +"); |
| mStkContext[slotId].mCurrentMenuCmd = mStkContext[slotId].mCurrentCmd; |
| mStkContext[slotId].mCurrentMenu = cmdMsg.getMenu(); |
| launchMenuActivity(cmdMsg.getMenu(), slotId); |
| break; |
| case SET_UP_MENU: |
| mStkContext[slotId].mCmdInProgress = false; |
| mStkContext[slotId].mMainCmd = mStkContext[slotId].mCurrentCmd; |
| mStkContext[slotId].mCurrentMenuCmd = mStkContext[slotId].mCurrentCmd; |
| mStkContext[slotId].mCurrentMenu = cmdMsg.getMenu(); |
| CatLog.d(LOG_TAG, "SET_UP_MENU [" + removeMenu(slotId) + "]"); |
| |
| if (removeMenu(slotId)) { |
| mStkContext[slotId].mCurrentMenu = null; |
| mStkContext[slotId].mMainCmd = null; |
| //Check other setup menu state. If all setup menu are removed, uninstall apk. |
| if (!uninstallIfUnnecessary()) { |
| addToMenuSystemOrUpdateLabel(); |
| } |
| } else { |
| addToMenuSystemOrUpdateLabel(); |
| } |
| if (mStkContext[slotId].mMenuIsVisible) { |
| launchMenuActivity(null, slotId); |
| } |
| break; |
| case GET_INPUT: |
| case GET_INKEY: |
| launchInputActivity(slotId); |
| break; |
| case SET_UP_IDLE_MODE_TEXT: |
| waitForUsersResponse = false; |
| mStkContext[slotId].mIdleModeTextCmd = mStkContext[slotId].mCurrentCmd; |
| TextMessage idleModeText = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| if (idleModeText == null || TextUtils.isEmpty(idleModeText.text)) { |
| cancelIdleText(slotId); |
| } |
| mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd; |
| if (mStkContext[slotId].mIdleModeTextCmd != null) { |
| if (mStkContext[slotId].mIdleModeTextVisible || isScreenIdle()) { |
| CatLog.d(LOG_TAG, "set up idle mode"); |
| launchIdleText(slotId); |
| } else { |
| registerHomeVisibilityObserver(); |
| } |
| } |
| break; |
| case SEND_DTMF: |
| case SEND_SMS: |
| case REFRESH: |
| case RUN_AT: |
| case SEND_SS: |
| case SEND_USSD: |
| case GET_CHANNEL_STATUS: |
| waitForUsersResponse = false; |
| launchEventMessage(slotId); |
| break; |
| case LAUNCH_BROWSER: |
| // The device setup process should not be interrupted by launching browser. |
| if (Settings.Global.getInt(mContext.getContentResolver(), |
| Settings.Global.DEVICE_PROVISIONED, 0) == 0) { |
| CatLog.d(LOG_TAG, "Not perform if the setup process has not been completed."); |
| sendScreenBusyResponse(slotId); |
| break; |
| } |
| |
| /* Check if Carrier would not want to launch browser */ |
| if (getBooleanCarrierConfig(CarrierConfigManager.KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL, |
| slotId)) { |
| CatLog.d(LOG_TAG, "Browser is not launched as per carrier."); |
| sendResponse(RES_ID_DONE, slotId, true); |
| break; |
| } |
| |
| mStkContext[slotId].mBrowserSettings = |
| mStkContext[slotId].mCurrentCmd.getBrowserSettings(); |
| if (!isUrlAvailableToLaunchBrowser(mStkContext[slotId].mBrowserSettings)) { |
| CatLog.d(LOG_TAG, "Browser url property is not set - send error"); |
| sendResponse(RES_ID_ERROR, slotId, true); |
| } else { |
| TextMessage alphaId = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| if ((alphaId == null) || TextUtils.isEmpty(alphaId.text)) { |
| // don't need user confirmation in this case |
| // just launch the browser or spawn a new tab |
| CatLog.d(LOG_TAG, "user confirmation is not currently needed.\n" + |
| "supressing confirmation dialogue and confirming silently..."); |
| mStkContext[slotId].launchBrowser = true; |
| sendResponse(RES_ID_CONFIRM, slotId, true); |
| } else { |
| launchConfirmationDialog(alphaId, slotId); |
| } |
| } |
| break; |
| case SET_UP_CALL: |
| TextMessage mesg = mStkContext[slotId].mCurrentCmd.getCallSettings().confirmMsg; |
| if((mesg != null) && (mesg.text == null || mesg.text.length() == 0)) { |
| mesg.text = getResources().getString(R.string.default_setup_call_msg); |
| } |
| CatLog.d(LOG_TAG, "SET_UP_CALL mesg.text " + mesg.text); |
| launchConfirmationDialog(mesg, slotId); |
| break; |
| case PLAY_TONE: |
| handlePlayTone(slotId); |
| break; |
| case OPEN_CHANNEL: |
| launchOpenChannelDialog(slotId); |
| break; |
| case CLOSE_CHANNEL: |
| case RECEIVE_DATA: |
| case SEND_DATA: |
| TextMessage m = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| |
| if ((m != null) && (m.text == null)) { |
| switch(cmdMsg.getCmdType()) { |
| case CLOSE_CHANNEL: |
| m.text = getResources().getString(R.string.default_close_channel_msg); |
| break; |
| case RECEIVE_DATA: |
| m.text = getResources().getString(R.string.default_receive_data_msg); |
| break; |
| case SEND_DATA: |
| m.text = getResources().getString(R.string.default_send_data_msg); |
| break; |
| } |
| } |
| /* |
| * Display indication in the form of a toast to the user if required. |
| */ |
| launchEventMessage(slotId); |
| break; |
| case SET_UP_EVENT_LIST: |
| replaceEventList(slotId); |
| if (isScreenIdle()) { |
| CatLog.d(LOG_TAG," Check if IDLE_SCREEN_AVAILABLE_EVENT is present in List"); |
| checkForSetupEvent(IDLE_SCREEN_AVAILABLE_EVENT, null, slotId); |
| } |
| break; |
| } |
| |
| if (!waitForUsersResponse) { |
| if (mStkContext[slotId].mCmdsQ.size() != 0) { |
| callDelayedMsg(slotId); |
| } else { |
| mStkContext[slotId].mCmdInProgress = false; |
| } |
| } |
| } |
| |
| private void addToMenuSystemOrUpdateLabel() { |
| String candidateLabel = null; |
| |
| for (int slotId = 0; slotId < mSimCount; slotId++) { |
| Menu menu = getMainMenu(slotId); |
| if (menu != null) { |
| if (!TextUtils.isEmpty(candidateLabel)) { |
| if (!TextUtils.equals(menu.title, candidateLabel)) { |
| // We should not display the alpha identifier of SET-UP MENU command |
| // as the application label on the application launcher |
| // if different alpha identifiers are provided by multiple SIMs. |
| candidateLabel = null; |
| break; |
| } |
| } else { |
| if (TextUtils.isEmpty(menu.title)) { |
| break; |
| } |
| candidateLabel = menu.title; |
| } |
| } |
| } |
| |
| StkAppInstaller.installOrUpdate(this, candidateLabel); |
| } |
| |
| @SuppressWarnings("FallThrough") |
| private void handleCmdResponse(Bundle args, int slotId) { |
| CatLog.d(LOG_TAG, "handleCmdResponse, sim id: " + slotId); |
| unregisterHomeKeyEventReceiver(); |
| if (mStkContext[slotId].mCurrentCmd == null) { |
| return; |
| } |
| |
| if (mStkService[slotId] == null) { |
| mStkService[slotId] = CatService.getInstance(slotId); |
| if (mStkService[slotId] == null) { |
| // CatService is disposed when the relevant SIM is removed or disabled. |
| // StkAppService can also be stopped when the absent state is notified, |
| // so this situation can happen. |
| CatLog.d(LOG_TAG, "No response is sent back to the missing CatService."); |
| return; |
| } |
| } |
| |
| CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentCmd); |
| |
| // set result code |
| boolean helpRequired = args.getBoolean(HELP, false); |
| boolean confirmed = false; |
| |
| switch(args.getInt(RES_ID)) { |
| case RES_ID_MENU_SELECTION: |
| CatLog.d(LOG_TAG, "MENU_SELECTION=" + mStkContext[slotId]. |
| mCurrentMenuCmd.getCmdType()); |
| int menuSelection = args.getInt(MENU_SELECTION); |
| switch(mStkContext[slotId].mCurrentMenuCmd.getCmdType()) { |
| case SET_UP_MENU: |
| mStkContext[slotId].mIsSessionFromUser = true; |
| // Fall through |
| case SELECT_ITEM: |
| mStkContext[slotId].lastSelectedItem = getItemName(menuSelection, slotId); |
| if (helpRequired) { |
| resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED); |
| } else { |
| resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ? |
| ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK); |
| } |
| resMsg.setMenuSelection(menuSelection); |
| break; |
| } |
| break; |
| case RES_ID_INPUT: |
| CatLog.d(LOG_TAG, "RES_ID_INPUT"); |
| String input = args.getString(INPUT); |
| if (input != null && (null != mStkContext[slotId].mCurrentCmd.geInput()) && |
| (mStkContext[slotId].mCurrentCmd.geInput().yesNo)) { |
| boolean yesNoSelection = input |
| .equals(StkInputActivity.YES_STR_RESPONSE); |
| resMsg.setYesNo(yesNoSelection); |
| } else { |
| if (helpRequired) { |
| resMsg.setResultCode(ResultCode.HELP_INFO_REQUIRED); |
| } else { |
| resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ? |
| ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK); |
| resMsg.setInput(input); |
| } |
| } |
| break; |
| case RES_ID_CONFIRM: |
| CatLog.d(LOG_TAG, "RES_ID_CONFIRM"); |
| confirmed = args.getBoolean(CONFIRMATION); |
| switch (mStkContext[slotId].mCurrentCmd.getCmdType()) { |
| case DISPLAY_TEXT: |
| if (confirmed) { |
| resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ? |
| ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK); |
| } else { |
| resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER); |
| } |
| break; |
| case LAUNCH_BROWSER: |
| resMsg.setResultCode(confirmed ? ResultCode.OK |
| : ResultCode.UICC_SESSION_TERM_BY_USER); |
| if (confirmed) { |
| mStkContext[slotId].launchBrowser = true; |
| mStkContext[slotId].mBrowserSettings = |
| mStkContext[slotId].mCurrentCmd.getBrowserSettings(); |
| } |
| break; |
| case SET_UP_CALL: |
| resMsg.setResultCode(ResultCode.OK); |
| resMsg.setConfirmation(confirmed); |
| if (confirmed) { |
| launchEventMessage(slotId, |
| mStkContext[slotId].mCurrentCmd.getCallSettings().callMsg); |
| } |
| break; |
| } |
| break; |
| case RES_ID_DONE: |
| resMsg.setResultCode(ResultCode.OK); |
| break; |
| case RES_ID_BACKWARD: |
| CatLog.d(LOG_TAG, "RES_ID_BACKWARD"); |
| resMsg.setResultCode(ResultCode.BACKWARD_MOVE_BY_USER); |
| break; |
| case RES_ID_END_SESSION: |
| CatLog.d(LOG_TAG, "RES_ID_END_SESSION"); |
| resMsg.setResultCode(ResultCode.UICC_SESSION_TERM_BY_USER); |
| break; |
| case RES_ID_TIMEOUT: |
| CatLog.d(LOG_TAG, "RES_ID_TIMEOUT"); |
| // GCF test-case 27.22.4.1.1 Expected Sequence 1.5 (DISPLAY TEXT, |
| // Clear message after delay, successful) expects result code OK. |
| // If the command qualifier specifies no user response is required |
| // then send OK instead of NO_RESPONSE_FROM_USER |
| if ((mStkContext[slotId].mCurrentCmd.getCmdType().value() == |
| AppInterface.CommandType.DISPLAY_TEXT.value()) |
| && (mStkContext[slotId].mCurrentCmd.geTextMessage().userClear == false)) { |
| resMsg.setResultCode(ResultCode.OK); |
| } else { |
| resMsg.setResultCode(ResultCode.NO_RESPONSE_FROM_USER); |
| } |
| break; |
| case RES_ID_CHOICE: |
| int choice = args.getInt(CHOICE); |
| CatLog.d(LOG_TAG, "User Choice=" + choice); |
| switch (choice) { |
| case YES: |
| resMsg.setResultCode(ResultCode.OK); |
| confirmed = true; |
| break; |
| case NO: |
| resMsg.setResultCode(ResultCode.USER_NOT_ACCEPT); |
| break; |
| } |
| |
| if (mStkContext[slotId].mCurrentCmd.getCmdType().value() == |
| AppInterface.CommandType.OPEN_CHANNEL.value()) { |
| resMsg.setConfirmation(confirmed); |
| } |
| break; |
| case RES_ID_ERROR: |
| CatLog.d(LOG_TAG, "RES_ID_ERROR"); |
| switch (mStkContext[slotId].mCurrentCmd.getCmdType()) { |
| case LAUNCH_BROWSER: |
| resMsg.setResultCode(ResultCode.LAUNCH_BROWSER_ERROR); |
| break; |
| } |
| break; |
| default: |
| CatLog.d(LOG_TAG, "Unknown result id"); |
| return; |
| } |
| |
| switch (args.getInt(RES_ID)) { |
| case RES_ID_MENU_SELECTION: |
| case RES_ID_INPUT: |
| case RES_ID_CONFIRM: |
| case RES_ID_CHOICE: |
| case RES_ID_BACKWARD: |
| case RES_ID_END_SESSION: |
| mStkContext[slotId].mNoResponseFromUser = false; |
| break; |
| case RES_ID_TIMEOUT: |
| cancelNotificationOnKeyguard(slotId); |
| mStkContext[slotId].mNoResponseFromUser = true; |
| break; |
| default: |
| // The other IDs cannot be used to judge if there is no response from user. |
| break; |
| } |
| |
| if (null != mStkContext[slotId].mCurrentCmd && |
| null != mStkContext[slotId].mCurrentCmd.getCmdType()) { |
| CatLog.d(LOG_TAG, "handleCmdResponse- cmdName[" + |
| mStkContext[slotId].mCurrentCmd.getCmdType().name() + "]"); |
| } |
| mStkService[slotId].onCmdResponse(resMsg); |
| } |
| |
| /** |
| * Returns 0 or FLAG_ACTIVITY_NO_USER_ACTION, 0 means the user initiated the action. |
| * |
| * @param userAction If the userAction is yes then we always return 0 otherwise |
| * mMenuIsVisible is used to determine what to return. If mMenuIsVisible is true |
| * then we are the foreground app and we'll return 0 as from our perspective a |
| * user action did cause. If it's false than we aren't the foreground app and |
| * FLAG_ACTIVITY_NO_USER_ACTION is returned. |
| * |
| * @return 0 or FLAG_ACTIVITY_NO_USER_ACTION |
| */ |
| private int getFlagActivityNoUserAction(InitiatedByUserAction userAction, int slotId) { |
| return ((userAction == InitiatedByUserAction.yes) | mStkContext[slotId].mMenuIsVisible) |
| ? 0 : Intent.FLAG_ACTIVITY_NO_USER_ACTION; |
| } |
| /** |
| * This method is used for cleaning up pending instances in stack. |
| * No terminal response will be sent for pending instances. |
| */ |
| private void cleanUpInstanceStackBySlot(int slotId) { |
| Activity activity = mStkContext[slotId].getPendingActivityInstance(); |
| Activity dialog = mStkContext[slotId].getPendingDialogInstance(); |
| CatLog.d(LOG_TAG, "cleanUpInstanceStackBySlot slotId: " + slotId); |
| if (activity != null) { |
| if (mStkContext[slotId].mCurrentCmd != null) { |
| CatLog.d(LOG_TAG, "current cmd type: " + |
| mStkContext[slotId].mCurrentCmd.getCmdType()); |
| if (mStkContext[slotId].mCurrentCmd.getCmdType().value() |
| == AppInterface.CommandType.GET_INPUT.value() |
| || mStkContext[slotId].mCurrentCmd.getCmdType().value() |
| == AppInterface.CommandType.GET_INKEY.value()) { |
| mStkContext[slotId].mIsInputPending = true; |
| } else if (mStkContext[slotId].mCurrentCmd.getCmdType().value() |
| == AppInterface.CommandType.SET_UP_MENU.value() |
| || mStkContext[slotId].mCurrentCmd.getCmdType().value() |
| == AppInterface.CommandType.SELECT_ITEM.value()) { |
| mStkContext[slotId].mIsMenuPending = true; |
| } |
| } |
| CatLog.d(LOG_TAG, "finish pending activity."); |
| activity.finish(); |
| mStkContext[slotId].mActivityInstance = null; |
| } |
| if (dialog != null) { |
| CatLog.d(LOG_TAG, "finish pending dialog."); |
| mStkContext[slotId].mIsDialogPending = true; |
| dialog.finish(); |
| mStkContext[slotId].mDialogInstance = null; |
| } |
| } |
| /** |
| * This method is used for restoring pending instances from stack. |
| */ |
| private void restoreInstanceFromStackBySlot(int slotId) { |
| AppInterface.CommandType cmdType = mStkContext[slotId].mCurrentCmd.getCmdType(); |
| |
| CatLog.d(LOG_TAG, "restoreInstanceFromStackBySlot cmdType : " + cmdType); |
| switch(cmdType) { |
| case GET_INPUT: |
| case GET_INKEY: |
| launchInputActivity(slotId); |
| //Set mMenuIsVisible to true for showing main menu for |
| //following session end command. |
| mStkContext[slotId].mMenuIsVisible = true; |
| break; |
| case DISPLAY_TEXT: |
| launchTextDialog(slotId); |
| break; |
| case LAUNCH_BROWSER: |
| launchConfirmationDialog(mStkContext[slotId].mCurrentCmd.geTextMessage(), |
| slotId); |
| break; |
| case OPEN_CHANNEL: |
| launchOpenChannelDialog(slotId); |
| break; |
| case SET_UP_CALL: |
| launchConfirmationDialog(mStkContext[slotId].mCurrentCmd.getCallSettings(). |
| confirmMsg, slotId); |
| break; |
| case SET_UP_MENU: |
| case SELECT_ITEM: |
| launchMenuActivity(null, slotId); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| @Override |
| public void startActivity(Intent intent) { |
| int slotId = intent.getIntExtra(SLOT_ID, SubscriptionManager.INVALID_SIM_SLOT_INDEX); |
| // Close the dialog displayed for DISPLAY TEXT command with an immediate response object |
| // before new dialog is displayed. |
| if (SubscriptionManager.isValidSlotIndex(slotId)) { |
| Activity dialog = mStkContext[slotId].getImmediateDialogInstance(); |
| if (dialog != null) { |
| CatLog.d(LOG_TAG, "finish dialog for immediate response."); |
| dialog.finish(); |
| } |
| } |
| super.startActivity(intent); |
| } |
| |
| private void launchMenuActivity(Menu menu, int slotId) { |
| Intent newIntent = new Intent(Intent.ACTION_VIEW); |
| String targetActivity = STK_MENU_ACTIVITY_NAME; |
| String uriString = STK_MENU_URI + System.currentTimeMillis(); |
| //Set unique URI to create a new instance of activity for different slotId. |
| Uri uriData = Uri.parse(uriString); |
| |
| CatLog.d(LOG_TAG, "launchMenuActivity, slotId: " + slotId + " , " + |
| uriData.toString() + " , " + mStkContext[slotId].mOpCode + ", " |
| + mStkContext[slotId].mMenuState); |
| newIntent.setClassName(PACKAGE_NAME, targetActivity); |
| int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK; |
| |
| if (menu == null) { |
| // We assume this was initiated by the user pressing the tool kit icon |
| intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.yes, slotId); |
| //If the last pending menu is secondary menu, "STATE" should be "STATE_SECONDARY". |
| //Otherwise, it should be "STATE_MAIN". |
| if (mStkContext[slotId].mOpCode == OP_LAUNCH_APP && |
| mStkContext[slotId].mMenuState == StkMenuActivity.STATE_SECONDARY) { |
| newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY); |
| } else { |
| newIntent.putExtra("STATE", StkMenuActivity.STATE_MAIN); |
| mStkContext[slotId].mMenuState = StkMenuActivity.STATE_MAIN; |
| } |
| } else { |
| // We don't know and we'll let getFlagActivityNoUserAction decide. |
| intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId); |
| newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY); |
| mStkContext[slotId].mMenuState = StkMenuActivity.STATE_SECONDARY; |
| } |
| if (mStkContext[slotId].mMenuState == StkMenuActivity.STATE_SECONDARY) { |
| startToObserveHomeKeyEvent(slotId); |
| } |
| newIntent.putExtra(SLOT_ID, slotId); |
| newIntent.setData(uriData); |
| newIntent.setFlags(intentFlags); |
| startActivity(newIntent); |
| } |
| |
| private void launchInputActivity(int slotId) { |
| Intent newIntent = new Intent(Intent.ACTION_VIEW); |
| String targetActivity = STK_INPUT_ACTIVITY_NAME; |
| String uriString = STK_INPUT_URI + System.currentTimeMillis(); |
| //Set unique URI to create a new instance of activity for different slotId. |
| Uri uriData = Uri.parse(uriString); |
| Input input = mStkContext[slotId].mCurrentCmd.geInput(); |
| |
| CatLog.d(LOG_TAG, "launchInputActivity, slotId: " + slotId); |
| newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId)); |
| newIntent.setClassName(PACKAGE_NAME, targetActivity); |
| newIntent.putExtra("INPUT", input); |
| newIntent.putExtra(SLOT_ID, slotId); |
| newIntent.setData(uriData); |
| |
| if (input != null) { |
| notifyUserIfNecessary(slotId, input.text); |
| } |
| startActivity(newIntent); |
| startToObserveHomeKeyEvent(slotId); |
| } |
| |
| private void launchTextDialog(int slotId) { |
| CatLog.d(LOG_TAG, "launchTextDialog, slotId: " + slotId); |
| Intent newIntent = new Intent(); |
| String targetActivity = STK_DIALOG_ACTIVITY_NAME; |
| int action = getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId); |
| String uriString = STK_DIALOG_URI + System.currentTimeMillis(); |
| //Set unique URI to create a new instance of activity for different slotId. |
| Uri uriData = Uri.parse(uriString); |
| TextMessage textMessage = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| |
| newIntent.setClassName(PACKAGE_NAME, targetActivity); |
| newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId)); |
| newIntent.setData(uriData); |
| newIntent.putExtra("TEXT", textMessage); |
| newIntent.putExtra(SLOT_ID, slotId); |
| |
| if (textMessage != null) { |
| notifyUserIfNecessary(slotId, textMessage.text); |
| } |
| startActivity(newIntent); |
| // For display texts with immediate response, send the terminal response |
| // immediately. responseNeeded will be false, if display text command has |
| // the immediate response tlv. |
| if (!mStkContext[slotId].mCurrentCmd.geTextMessage().responseNeeded) { |
| sendResponse(RES_ID_CONFIRM, slotId, true); |
| } else { |
| startToObserveHomeKeyEvent(slotId); |
| } |
| } |
| |
| private void notifyUserIfNecessary(int slotId, String message) { |
| createAllChannels(); |
| |
| if (mStkContext[slotId].mNoResponseFromUser) { |
| // No response from user was observed in the current session. |
| // Do nothing in that case in order to avoid turning on the screen again and again |
| // when the card repeatedly sends the same command in its retry procedure. |
| return; |
| } |
| |
| PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); |
| |
| if (((KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE)).isKeyguardLocked()) { |
| // Display the notification on the keyguard screen |
| // if user cannot see the message from the card right now because of it. |
| // The notification can be dismissed if user removed the keyguard screen. |
| launchNotificationOnKeyguard(slotId, message); |
| } |
| |
| // Turn on the screen. |
| PowerManager.WakeLock wakelock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |
| | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, LOG_TAG); |
| wakelock.acquire(); |
| wakelock.release(); |
| } |
| |
| private void launchNotificationOnKeyguard(int slotId, String message) { |
| Notification.Builder builder = new Notification.Builder(this, STK_NOTIFICATION_CHANNEL_ID); |
| setNotificationTitle(slotId, builder); |
| |
| builder.setStyle(new Notification.BigTextStyle(builder).bigText(message)); |
| builder.setContentText(message); |
| builder.setSmallIcon(R.drawable.stat_notify_sim_toolkit); |
| builder.setOngoing(true); |
| builder.setOnlyAlertOnce(true); |
| builder.setColor(getResources().getColor( |
| com.android.internal.R.color.system_notification_accent_color)); |
| Intent userPresentIntent = new Intent(mContext, UserPresentReceiver.class); |
| userPresentIntent.setAction(Intent.ACTION_USER_PRESENT); |
| PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, |
| NOTIFICATION_PENDING_INTENT_REQUEST_CODE, userPresentIntent, |
| PendingIntent.FLAG_IMMUTABLE); |
| builder.setContentIntent(pendingIntent); |
| mNotificationManager.notify(getNotificationId(NOTIFICATION_ON_KEYGUARD, slotId), |
| builder.build()); |
| mStkContext[slotId].mNotificationOnKeyguard = true; |
| } |
| |
| public void cancelNotificationOnKeyguard() { |
| for (int slot = 0; slot < mSimCount; slot++) { |
| cancelNotificationOnKeyguard(slot); |
| } |
| } |
| |
| private void cancelNotificationOnKeyguard(int slotId) { |
| mNotificationManager.cancel(getNotificationId(NOTIFICATION_ON_KEYGUARD, slotId)); |
| mStkContext[slotId].mNotificationOnKeyguard = false; |
| } |
| |
| private int getNotificationId(int notificationType, int slotId) { |
| return getNotificationId(slotId) + (notificationType * mSimCount); |
| } |
| |
| private void replaceEventList(int slotId) { |
| if (mStkContext[slotId].mSetupEventListSettings != null) { |
| for (int current : mStkContext[slotId].mSetupEventListSettings.eventList) { |
| if (current != INVALID_SETUP_EVENT) { |
| // Cancel the event notification if it is not listed in the new event list. |
| if ((mStkContext[slotId].mCurrentCmd.getSetEventList() == null) |
| || !findEvent(current, mStkContext[slotId].mCurrentCmd |
| .getSetEventList().eventList)) { |
| unregisterEvent(current, slotId); |
| } |
| } |
| } |
| } |
| mStkContext[slotId].mSetupEventListSettings |
| = mStkContext[slotId].mCurrentCmd.getSetEventList(); |
| mStkContext[slotId].mCurrentSetupEventCmd = mStkContext[slotId].mCurrentCmd; |
| mStkContext[slotId].mCurrentCmd = mStkContext[slotId].mMainCmd; |
| registerEvents(slotId); |
| } |
| |
| private boolean findEvent(int event, int[] eventList) { |
| for (int content : eventList) { |
| if (content == event) return true; |
| } |
| return false; |
| } |
| |
| private void unregisterEvent(int event, int slotId) { |
| for (int slot = 0; slot < mSimCount; slot++) { |
| if (slot != slotId) { |
| if (mStkContext[slot].mSetupEventListSettings != null) { |
| if (findEvent(event, mStkContext[slot].mSetupEventListSettings.eventList)) { |
| // The specified event shall never be canceled |
| // if there is any other SIM card which requests the event. |
| return; |
| } |
| } |
| } |
| } |
| |
| switch (event) { |
| case USER_ACTIVITY_EVENT: |
| unregisterUserActivityReceiver(); |
| break; |
| case IDLE_SCREEN_AVAILABLE_EVENT: |
| unregisterHomeVisibilityObserver(AppInterface.CommandType.SET_UP_EVENT_LIST, slotId); |
| break; |
| case LANGUAGE_SELECTION_EVENT: |
| unregisterLocaleChangeReceiver(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| private void registerEvents(int slotId) { |
| if (mStkContext[slotId].mSetupEventListSettings == null) { |
| return; |
| } |
| for (int event : mStkContext[slotId].mSetupEventListSettings.eventList) { |
| switch (event) { |
| case USER_ACTIVITY_EVENT: |
| registerUserActivityReceiver(); |
| break; |
| case IDLE_SCREEN_AVAILABLE_EVENT: |
| registerHomeVisibilityObserver(); |
| break; |
| case LANGUAGE_SELECTION_EVENT: |
| registerLocaleChangeReceiver(); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| private synchronized void registerUserActivityReceiver() { |
| if (mUserActivityReceiver == null) { |
| mUserActivityReceiver = new BroadcastReceiver() { |
| @Override public void onReceive(Context context, Intent intent) { |
| if (TelephonyIntents.ACTION_USER_ACTIVITY_NOTIFICATION.equals( |
| intent.getAction())) { |
| Message message = mServiceHandler.obtainMessage(OP_USER_ACTIVITY); |
| mServiceHandler.sendMessage(message); |
| unregisterUserActivityReceiver(); |
| } |
| } |
| }; |
| registerReceiver(mUserActivityReceiver, new IntentFilter( |
| TelephonyIntents.ACTION_USER_ACTIVITY_NOTIFICATION)); |
| try { |
| ITelephony telephony = ITelephony.Stub.asInterface( |
| TelephonyFrameworkInitializer |
| .getTelephonyServiceManager() |
| .getTelephonyServiceRegisterer() |
| .get()); |
| telephony.requestUserActivityNotification(); |
| } catch (RemoteException e) { |
| CatLog.e(LOG_TAG, "failed to init WindowManager:" + e); |
| } |
| } |
| } |
| |
| private synchronized void unregisterUserActivityReceiver() { |
| if (mUserActivityReceiver != null) { |
| unregisterReceiver(mUserActivityReceiver); |
| mUserActivityReceiver = null; |
| } |
| } |
| |
| private synchronized void registerHomeVisibilityObserver() { |
| if (mHomeVisibilityListener == null) { |
| mHomeVisibilityListener = new HomeVisibilityListener() { |
| @Override |
| public void onHomeVisibilityChanged(boolean isHomeActivityVisible) { |
| if (isHomeActivityVisible) { |
| Message message = mServiceHandler.obtainMessage(OP_IDLE_SCREEN); |
| mServiceHandler.sendMessage(message); |
| unregisterHomeVisibilityObserver(); |
| } |
| } |
| }; |
| ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); |
| am.addHomeVisibilityListener(Runnable::run, mHomeVisibilityListener); |
| CatLog.d(LOG_TAG, "Started to observe the foreground activity"); |
| } |
| } |
| |
| private void unregisterHomeVisibilityObserver(AppInterface.CommandType command, int slotId) { |
| // Check if there is any pending command which still needs the process observer |
| // except for the current command and slot. |
| for (int slot = 0; slot < mSimCount; slot++) { |
| if (command != AppInterface.CommandType.SET_UP_IDLE_MODE_TEXT || slot != slotId) { |
| if (mStkContext[slot].mIdleModeTextCmd != null |
| && !mStkContext[slot].mIdleModeTextVisible) { |
| // Keep the process observer registered |
| // as there is an idle mode text which has not been visible yet. |
| return; |
| } |
| } |
| if (command != AppInterface.CommandType.SET_UP_EVENT_LIST || slot != slotId) { |
| if (mStkContext[slot].mSetupEventListSettings != null) { |
| if (findEvent(IDLE_SCREEN_AVAILABLE_EVENT, |
| mStkContext[slot].mSetupEventListSettings.eventList)) { |
| // Keep the process observer registered |
| // as there is a SIM card which still want IDLE SCREEN AVAILABLE event. |
| return; |
| } |
| } |
| } |
| } |
| unregisterHomeVisibilityObserver(); |
| } |
| |
| private synchronized void unregisterHomeVisibilityObserver() { |
| if (mHomeVisibilityListener != null) { |
| ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); |
| am.removeHomeVisibilityListener(mHomeVisibilityListener); |
| CatLog.d(LOG_TAG, "Stopped to observe the foreground activity"); |
| mHomeVisibilityListener = null; |
| } |
| } |
| |
| private synchronized void registerLocaleChangeReceiver() { |
| if (mLocaleChangeReceiver == null) { |
| mLocaleChangeReceiver = new BroadcastReceiver() { |
| @Override public void onReceive(Context context, Intent intent) { |
| if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { |
| Message message = mServiceHandler.obtainMessage(OP_LOCALE_CHANGED); |
| mServiceHandler.sendMessage(message); |
| } |
| } |
| }; |
| registerReceiver(mLocaleChangeReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); |
| } |
| } |
| |
| private synchronized void unregisterLocaleChangeReceiver() { |
| if (mLocaleChangeReceiver != null) { |
| unregisterReceiver(mLocaleChangeReceiver); |
| mLocaleChangeReceiver = null; |
| } |
| } |
| |
| private void sendSetUpEventResponse(int event, byte[] addedInfo, int slotId) { |
| CatLog.d(LOG_TAG, "sendSetUpEventResponse: event : " + event + "slotId = " + slotId); |
| |
| if (mStkContext[slotId].mCurrentSetupEventCmd == null){ |
| CatLog.e(LOG_TAG, "mCurrentSetupEventCmd is null"); |
| return; |
| } |
| |
| CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentSetupEventCmd); |
| |
| resMsg.setResultCode(ResultCode.OK); |
| resMsg.setEventDownload(event, addedInfo); |
| |
| mStkService[slotId].onCmdResponse(resMsg); |
| } |
| |
| private void checkForSetupEvent(int event, Bundle args, int slotId) { |
| boolean eventPresent = false; |
| byte[] addedInfo = null; |
| CatLog.d(LOG_TAG, "Event :" + event); |
| |
| if (mStkContext[slotId].mSetupEventListSettings != null) { |
| /* Checks if the event is present in the EventList updated by last |
| * SetupEventList Proactive Command */ |
| for (int i : mStkContext[slotId].mSetupEventListSettings.eventList) { |
| if (event == i) { |
| eventPresent = true; |
| break; |
| } |
| } |
| |
| /* If Event is present send the response to ICC */ |
| if (eventPresent == true) { |
| CatLog.d(LOG_TAG, " Event " + event + "exists in the EventList"); |
| |
| switch (event) { |
| case USER_ACTIVITY_EVENT: |
| case IDLE_SCREEN_AVAILABLE_EVENT: |
| sendSetUpEventResponse(event, addedInfo, slotId); |
| removeSetUpEvent(event, slotId); |
| break; |
| case LANGUAGE_SELECTION_EVENT: |
| String language = mContext |
| .getResources().getConfiguration().locale.getLanguage(); |
| CatLog.d(LOG_TAG, "language: " + language); |
| // Each language code is a pair of alpha-numeric characters. |
| // Each alpha-numeric character shall be coded on one byte |
| // using the SMS default 7-bit coded alphabet |
| addedInfo = GsmAlphabet.stringToGsm8BitPacked(language); |
| sendSetUpEventResponse(event, addedInfo, slotId); |
| break; |
| default: |
| break; |
| } |
| } else { |
| CatLog.e(LOG_TAG, " Event does not exist in the EventList"); |
| } |
| } else { |
| CatLog.e(LOG_TAG, "SetupEventList is not received. Ignoring the event: " + event); |
| } |
| } |
| |
| private void removeSetUpEvent(int event, int slotId) { |
| CatLog.d(LOG_TAG, "Remove Event :" + event); |
| |
| if (mStkContext[slotId].mSetupEventListSettings != null) { |
| /* |
| * Make new Eventlist without the event |
| */ |
| for (int i = 0; i < mStkContext[slotId].mSetupEventListSettings.eventList.length; i++) { |
| if (event == mStkContext[slotId].mSetupEventListSettings.eventList[i]) { |
| mStkContext[slotId].mSetupEventListSettings.eventList[i] = INVALID_SETUP_EVENT; |
| |
| switch (event) { |
| case USER_ACTIVITY_EVENT: |
| // The broadcast receiver can be unregistered |
| // as the event has already been sent to the card. |
| unregisterUserActivityReceiver(); |
| break; |
| case IDLE_SCREEN_AVAILABLE_EVENT: |
| // The process observer can be unregistered |
| // as the idle screen has already been available. |
| unregisterHomeVisibilityObserver(); |
| break; |
| default: |
| break; |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| private void launchEventMessage(int slotId) { |
| launchEventMessage(slotId, mStkContext[slotId].mCurrentCmd.geTextMessage()); |
| } |
| |
| private void launchEventMessage(int slotId, TextMessage msg) { |
| if (msg == null || msg.text == null || (msg.text != null && msg.text.length() == 0)) { |
| CatLog.d(LOG_TAG, "launchEventMessage return"); |
| return; |
| } |
| |
| Toast toast = new Toast(mContext.getApplicationContext()); |
| LayoutInflater inflate = (LayoutInflater) mContext |
| .getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| View v = inflate.inflate(R.layout.stk_event_msg, null); |
| TextView tv = (TextView) v |
| .findViewById(com.android.internal.R.id.message); |
| ImageView iv = (ImageView) v |
| .findViewById(com.android.internal.R.id.icon); |
| if (msg.icon != null) { |
| iv.setImageBitmap(msg.icon); |
| } else { |
| iv.setVisibility(View.GONE); |
| } |
| /* In case of 'self explanatory' stkapp should display the specified |
| * icon in proactive command (but not the alpha string). |
| * If icon is non-self explanatory and if the icon could not be displayed |
| * then alpha string or text data should be displayed |
| * Ref: ETSI 102.223,section 6.5.4 |
| */ |
| if (mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() || |
| msg.icon == null || !msg.iconSelfExplanatory) { |
| tv.setText(msg.text); |
| } |
| |
| toast.setView(v); |
| toast.setDuration(Toast.LENGTH_LONG); |
| toast.setGravity(Gravity.BOTTOM, 0, 0); |
| toast.show(); |
| } |
| |
| private void launchConfirmationDialog(TextMessage msg, int slotId) { |
| msg.title = mStkContext[slotId].lastSelectedItem; |
| Intent newIntent = new Intent(); |
| String targetActivity = STK_DIALOG_ACTIVITY_NAME; |
| String uriString = STK_DIALOG_URI + System.currentTimeMillis(); |
| //Set unique URI to create a new instance of activity for different slotId. |
| Uri uriData = Uri.parse(uriString); |
| |
| newIntent.setClassName(this, targetActivity); |
| newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_NO_HISTORY |
| | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
| | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId)); |
| newIntent.putExtra("TEXT", msg); |
| newIntent.putExtra(SLOT_ID, slotId); |
| newIntent.setData(uriData); |
| startActivity(newIntent); |
| } |
| |
| private void launchBrowser(BrowserSettings settings) { |
| if (settings == null) { |
| return; |
| } |
| |
| Uri data = null; |
| String url; |
| if (settings.url == null) { |
| // if the command did not contain a URL, |
| // launch the browser to the default homepage. |
| CatLog.d(LOG_TAG, "no url data provided by proactive command." + |
| " launching browser with stk default URL ... "); |
| url = SystemProperties.get(STK_BROWSER_DEFAULT_URL_SYSPROP, |
| "http://www.google.com"); |
| } else { |
| CatLog.d(LOG_TAG, "launch browser command has attached url = " + settings.url); |
| url = settings.url; |
| } |
| |
| if (url.startsWith("http://") || url.startsWith("https://")) { |
| data = Uri.parse(url); |
| CatLog.d(LOG_TAG, "launching browser with url = " + url); |
| } else { |
| String modifiedUrl = "http://" + url; |
| data = Uri.parse(modifiedUrl); |
| CatLog.d(LOG_TAG, "launching browser with modified url = " + modifiedUrl); |
| } |
| |
| Intent intent = new Intent(Intent.ACTION_VIEW); |
| intent.setData(data); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| switch (settings.mode) { |
| case USE_EXISTING_BROWSER: |
| intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| break; |
| case LAUNCH_NEW_BROWSER: |
| intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); |
| break; |
| case LAUNCH_IF_NOT_ALREADY_LAUNCHED: |
| intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| break; |
| } |
| // start browser activity |
| startActivity(intent); |
| // a small delay, let the browser start, before processing the next command. |
| // this is good for scenarios where a related DISPLAY TEXT command is |
| // followed immediately. |
| try { |
| Thread.sleep(3000); |
| } catch (InterruptedException e) {} |
| } |
| |
| private void cancelIdleText(int slotId) { |
| unregisterHomeVisibilityObserver(AppInterface.CommandType.SET_UP_IDLE_MODE_TEXT, slotId); |
| mNotificationManager.cancel(getNotificationId(slotId)); |
| mStkContext[slotId].mIdleModeTextCmd = null; |
| mStkContext[slotId].mIdleModeTextVisible = false; |
| } |
| |
| private void launchIdleText(int slotId) { |
| TextMessage msg = mStkContext[slotId].mIdleModeTextCmd.geTextMessage(); |
| |
| if (msg != null && !TextUtils.isEmpty(msg.text)) { |
| CatLog.d(LOG_TAG, "launchIdleText - text[" + msg.text |
| + "] iconSelfExplanatory[" + msg.iconSelfExplanatory |
| + "] icon[" + msg.icon + "], sim id: " + slotId); |
| CatLog.d(LOG_TAG, "Add IdleMode text"); |
| PendingIntent pendingIntent = PendingIntent.getService(mContext, 0, |
| new Intent(mContext, StkAppService.class), PendingIntent.FLAG_IMMUTABLE); |
| createAllChannels(); |
| final Notification.Builder notificationBuilder = new Notification.Builder( |
| StkAppService.this, STK_NOTIFICATION_CHANNEL_ID); |
| setNotificationTitle(slotId, notificationBuilder); |
| notificationBuilder |
| .setSmallIcon(R.drawable.stat_notify_sim_toolkit); |
| notificationBuilder.setContentIntent(pendingIntent); |
| notificationBuilder.setOngoing(true); |
| notificationBuilder.setOnlyAlertOnce(true); |
| // Set text and icon for the status bar and notification body. |
| if (mStkContext[slotId].mIdleModeTextCmd.hasIconLoadFailed() || |
| !msg.iconSelfExplanatory) { |
| notificationBuilder.setContentText(msg.text); |
| notificationBuilder.setTicker(msg.text); |
| notificationBuilder.setStyle(new Notification.BigTextStyle(notificationBuilder) |
| .bigText(msg.text)); |
| } |
| if (msg.icon != null) { |
| notificationBuilder.setLargeIcon(msg.icon); |
| } else { |
| Bitmap bitmapIcon = BitmapFactory.decodeResource(StkAppService.this |
| .getResources().getSystem(), |
| R.drawable.stat_notify_sim_toolkit); |
| notificationBuilder.setLargeIcon(bitmapIcon); |
| } |
| notificationBuilder.setColor(mContext.getResources().getColor( |
| com.android.internal.R.color.system_notification_accent_color)); |
| mNotificationManager.notify(getNotificationId(slotId), notificationBuilder.build()); |
| mStkContext[slotId].mIdleModeTextVisible = true; |
| } |
| } |
| |
| private void setNotificationTitle(int slotId, Notification.Builder builder) { |
| Menu menu = getMainMenu(slotId); |
| if (menu == null || TextUtils.isEmpty(menu.title) |
| || TextUtils.equals(menu.title, getResources().getString(R.string.app_name))) { |
| // No need to set a content title in the content area if no title (alpha identifier |
| // of SET-UP MENU command) is available for the specified slot or the title is same |
| // as the application label. |
| return; |
| } |
| |
| for (int index = 0; index < mSimCount; index++) { |
| if (index != slotId) { |
| Menu otherMenu = getMainMenu(index); |
| if (otherMenu != null && !TextUtils.equals(menu.title, otherMenu.title)) { |
| // Set the title (alpha identifier of SET-UP MENU command) as the content title |
| // to differentiate it from other main menu with different alpha identifier |
| // (including null) is available. |
| builder.setContentTitle(menu.title); |
| return; |
| } |
| } |
| } |
| } |
| |
| /** Creates the notification channel and registers it with NotificationManager. |
| * If a channel with the same ID is already registered, NotificationManager will |
| * ignore this call. |
| */ |
| private void createAllChannels() { |
| NotificationChannel notificationChannel = new NotificationChannel( |
| STK_NOTIFICATION_CHANNEL_ID, |
| getResources().getString(R.string.stk_channel_name), |
| NotificationManager.IMPORTANCE_DEFAULT); |
| |
| notificationChannel.enableVibration(true); |
| notificationChannel.setVibrationPattern(VIBRATION_PATTERN); |
| |
| mNotificationManager.createNotificationChannel(notificationChannel); |
| } |
| |
| private void launchToneDialog(int slotId) { |
| Intent newIntent = new Intent(this, ToneDialog.class); |
| String uriString = STK_TONE_URI + slotId; |
| Uri uriData = Uri.parse(uriString); |
| //Set unique URI to create a new instance of activity for different slotId. |
| CatLog.d(LOG_TAG, "launchToneDialog, slotId: " + slotId); |
| newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_NO_HISTORY |
| | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
| | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId)); |
| newIntent.putExtra("TEXT", mStkContext[slotId].mCurrentCmd.geTextMessage()); |
| newIntent.putExtra("TONE", mStkContext[slotId].mCurrentCmd.getToneSettings()); |
| newIntent.putExtra(SLOT_ID, slotId); |
| newIntent.setData(uriData); |
| startActivity(newIntent); |
| } |
| |
| private void handlePlayTone(int slotId) { |
| TextMessage toneMsg = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| |
| boolean showUser = true; |
| boolean displayDialog = true; |
| Resources resource = Resources.getSystem(); |
| try { |
| displayDialog = !resource.getBoolean( |
| R.bool.config_stkNoAlphaUsrCnf); |
| } catch (NotFoundException e) { |
| displayDialog = true; |
| } |
| |
| // As per the spec 3GPP TS 11.14, 6.4.5. Play Tone. |
| // If there is no alpha identifier tlv present, UE may show the |
| // user information. 'config_stkNoAlphaUsrCnf' value will decide |
| // whether to show it or not. |
| // If alpha identifier tlv is present and its data is null, play only tone |
| // without showing user any information. |
| // Alpha Id is Present, but the text data is null. |
| if ((toneMsg.text != null ) && (toneMsg.text.equals(""))) { |
| CatLog.d(LOG_TAG, "Alpha identifier data is null, play only tone"); |
| showUser = false; |
| } |
| // Alpha Id is not present AND we need to show info to the user. |
| if (toneMsg.text == null && displayDialog) { |
| CatLog.d(LOG_TAG, "toneMsg.text " + toneMsg.text |
| + " Starting ToneDialog activity with default message."); |
| toneMsg.text = getResources().getString(R.string.default_tone_dialog_msg); |
| showUser = true; |
| } |
| // Dont show user info, if config setting is true. |
| if (toneMsg.text == null && !displayDialog) { |
| CatLog.d(LOG_TAG, "config value stkNoAlphaUsrCnf is true"); |
| showUser = false; |
| } |
| |
| CatLog.d(LOG_TAG, "toneMsg.text: " + toneMsg.text + "showUser: " +showUser + |
| "displayDialog: " +displayDialog); |
| playTone(showUser, slotId); |
| } |
| |
| private void playTone(boolean showUserInfo, int slotId) { |
| // Start playing tone and vibration |
| ToneSettings settings = mStkContext[slotId].mCurrentCmd.getToneSettings(); |
| if (null == settings) { |
| CatLog.d(LOG_TAG, "null settings, not playing tone."); |
| return; |
| } |
| |
| mVibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE); |
| mTonePlayer = new TonePlayer(); |
| mTonePlayer.play(settings.tone); |
| int timeout = StkApp.calculateDurationInMilis(settings.duration); |
| if (timeout == 0) { |
| timeout = StkApp.TONE_DEFAULT_TIMEOUT; |
| } |
| |
| Message msg = mServiceHandler.obtainMessage(OP_STOP_TONE, 0, slotId, |
| (showUserInfo ? PLAY_TONE_WITH_DIALOG : PLAY_TONE_ONLY)); |
| mServiceHandler.sendMessageDelayed(msg, timeout); |
| if (settings.vibrate) { |
| mVibrator.vibrate(timeout); |
| } |
| |
| // Start Tone dialog Activity to show user the information. |
| if (showUserInfo) { |
| Intent newIntent = new Intent(sInstance, ToneDialog.class); |
| String uriString = STK_TONE_URI + slotId; |
| Uri uriData = Uri.parse(uriString); |
| newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_SINGLE_TOP |
| | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId)); |
| newIntent.putExtra("TEXT", mStkContext[slotId].mCurrentCmd.geTextMessage()); |
| newIntent.putExtra(SLOT_ID, slotId); |
| newIntent.setData(uriData); |
| startActivity(newIntent); |
| } |
| } |
| |
| private void finishToneDialogActivity() { |
| Intent finishIntent = new Intent(FINISH_TONE_ACTIVITY_ACTION); |
| sendBroadcast(finishIntent); |
| } |
| |
| private void handleStopTone(Message msg, int slotId) { |
| int resId = 0; |
| |
| // Stop the play tone in following cases: |
| // 1.OP_STOP_TONE: play tone timer expires. |
| // 2.STOP_TONE_USER: user pressed the back key. |
| if (msg.what == OP_STOP_TONE) { |
| resId = RES_ID_DONE; |
| // Dismiss Tone dialog, after finishing off playing the tone. |
| if (PLAY_TONE_WITH_DIALOG.equals((Integer) msg.obj)) finishToneDialogActivity(); |
| } else if (msg.what == OP_STOP_TONE_USER) { |
| resId = RES_ID_END_SESSION; |
| } |
| |
| sendResponse(resId, slotId, true); |
| |
| mServiceHandler.removeMessages(OP_STOP_TONE); |
| mServiceHandler.removeMessages(OP_STOP_TONE_USER); |
| |
| if (mTonePlayer != null) { |
| mTonePlayer.stop(); |
| mTonePlayer.release(); |
| mTonePlayer = null; |
| } |
| if (mVibrator != null) { |
| mVibrator.cancel(); |
| mVibrator = null; |
| } |
| } |
| |
| boolean isNoTonePlaying() { |
| return mTonePlayer == null ? true : false; |
| } |
| |
| private void launchOpenChannelDialog(final int slotId) { |
| TextMessage msg = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| if (msg == null) { |
| CatLog.d(LOG_TAG, "msg is null, return here"); |
| return; |
| } |
| |
| msg.title = getResources().getString(R.string.stk_dialog_title); |
| if (msg.text == null) { |
| msg.text = getResources().getString(R.string.default_open_channel_msg); |
| } |
| |
| mAlertDialog = new AlertDialog.Builder(mContext) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setTitle(msg.title) |
| .setMessage(msg.text) |
| .setCancelable(false) |
| .setPositiveButton(getResources().getString(R.string.stk_dialog_accept), |
| new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| Bundle args = new Bundle(); |
| args.putInt(RES_ID, RES_ID_CHOICE); |
| args.putInt(CHOICE, YES); |
| sendResponse(args, slotId); |
| } |
| }) |
| .setNegativeButton(getResources().getString(R.string.stk_dialog_reject), |
| new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| Bundle args = new Bundle(); |
| args.putInt(RES_ID, RES_ID_CHOICE); |
| args.putInt(CHOICE, NO); |
| sendResponse(args, slotId); |
| } |
| }) |
| .create(); |
| |
| mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| if (!mContext.getResources().getBoolean( |
| R.bool.config_sf_slowBlur)) { |
| mAlertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); |
| } |
| |
| mAlertDialog.show(); |
| } |
| |
| private void launchTransientEventMessage(int slotId) { |
| TextMessage msg = mStkContext[slotId].mCurrentCmd.geTextMessage(); |
| if (msg == null) { |
| CatLog.d(LOG_TAG, "msg is null, return here"); |
| return; |
| } |
| |
| msg.title = getResources().getString(R.string.stk_dialog_title); |
| |
| final AlertDialog dialog = new AlertDialog.Builder(mContext) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setTitle(msg.title) |
| .setMessage(msg.text) |
| .setCancelable(false) |
| .setPositiveButton(getResources().getString(android.R.string.ok), |
| new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| } |
| }) |
| .create(); |
| |
| dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| if (!mContext.getResources().getBoolean(R.bool.config_sf_slowBlur)) { |
| dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); |
| } |
| |
| dialog.show(); |
| } |
| |
| private int getNotificationId(int slotId) { |
| int notifyId = STK_NOTIFICATION_ID; |
| if (slotId >= 0 && slotId < mSimCount) { |
| notifyId += slotId; |
| } else { |
| CatLog.d(LOG_TAG, "invalid slotId: " + slotId); |
| } |
| CatLog.d(LOG_TAG, "getNotificationId, slotId: " + slotId + ", notifyId: " + notifyId); |
| return notifyId; |
| } |
| |
| private String getItemName(int itemId, int slotId) { |
| Menu menu = mStkContext[slotId].mCurrentCmd.getMenu(); |
| if (menu == null) { |
| return null; |
| } |
| for (Item item : menu.items) { |
| if (item.id == itemId) { |
| return item.text; |
| } |
| } |
| return null; |
| } |
| |
| private boolean removeMenu(int slotId) { |
| try { |
| if (mStkContext[slotId].mCurrentMenu.items.size() == 1 && |
| mStkContext[slotId].mCurrentMenu.items.get(0) == null) { |
| return true; |
| } |
| } catch (NullPointerException e) { |
| CatLog.d(LOG_TAG, "Unable to get Menu's items size"); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean uninstallIfUnnecessary() { |
| for (int slot = 0; slot < mSimCount; slot++) { |
| if (mStkContext[slot].mMainCmd != null) { |
| return false; |
| } |
| } |
| CatLog.d(LOG_TAG, "Uninstall App"); |
| StkAppInstaller.uninstall(this); |
| return true; |
| } |
| |
| synchronized StkContext getStkContext(int slotId) { |
| if (slotId >= 0 && slotId < mSimCount) { |
| return mStkContext[slotId]; |
| } else { |
| CatLog.d(LOG_TAG, "invalid slotId: " + slotId); |
| return null; |
| } |
| } |
| |
| private void handleAlphaNotify(Bundle args) { |
| String alphaString = args.getString(AppInterface.ALPHA_STRING); |
| |
| CatLog.d(LOG_TAG, "Alpha string received from card: " + alphaString); |
| Toast toast = Toast.makeText(sInstance, alphaString, Toast.LENGTH_LONG); |
| toast.setGravity(Gravity.TOP, 0, 0); |
| toast.show(); |
| } |
| |
| private boolean isUrlAvailableToLaunchBrowser(BrowserSettings settings) { |
| String url = SystemProperties.get(STK_BROWSER_DEFAULT_URL_SYSPROP, ""); |
| if (url == "" && settings.url == null) { |
| return false; |
| } |
| return true; |
| } |
| } |