1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.stk; 18 19 import android.app.ActionBar; 20 import android.app.AlarmManager; 21 import android.app.ListActivity; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.os.Bundle; 27 import android.os.SystemClock; 28 import android.telephony.SubscriptionManager; 29 import android.view.ContextMenu; 30 import android.view.ContextMenu.ContextMenuInfo; 31 import android.view.KeyEvent; 32 import android.view.MenuItem; 33 import android.view.View; 34 import android.view.WindowManager; 35 import android.widget.AdapterView; 36 import android.widget.ImageView; 37 import android.widget.ListView; 38 import android.widget.ProgressBar; 39 import android.widget.TextView; 40 41 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 42 43 import com.android.internal.telephony.cat.CatLog; 44 import com.android.internal.telephony.cat.Item; 45 import com.android.internal.telephony.cat.Menu; 46 47 /** 48 * ListActivity used for displaying STK menus. These can be SET UP MENU and 49 * SELECT ITEM menus. This activity is started multiple times with different 50 * menu content. 51 * 52 */ 53 public class StkMenuActivity extends ListActivity implements View.OnCreateContextMenuListener { 54 private Menu mStkMenu = null; 55 private int mState = STATE_MAIN; 56 private boolean mAcceptUsersInput = true; 57 private int mSlotId = -1; 58 private boolean mIsResponseSent = false; 59 60 private TextView mTitleTextView = null; 61 private ImageView mTitleIconView = null; 62 private ProgressBar mProgressView = null; 63 64 private static final String LOG_TAG = StkMenuActivity.class.getSimpleName(); 65 66 private StkAppService appService = StkAppService.getInstance(); 67 68 // Keys for saving the state of the dialog in the bundle 69 private static final String STATE_KEY = "state"; 70 private static final String ACCEPT_USERS_INPUT_KEY = "accept_users_input"; 71 private static final String RESPONSE_SENT_KEY = "response_sent"; 72 private static final String ALARM_TIME_KEY = "alarm_time"; 73 74 private static final String SELECT_ALARM_TAG = LOG_TAG; 75 private static final long NO_SELECT_ALARM = -1; 76 private long mAlarmTime = NO_SELECT_ALARM; 77 78 // Internal state values 79 static final int STATE_INIT = 0; 80 static final int STATE_MAIN = 1; 81 static final int STATE_SECONDARY = 2; 82 83 private static final int CONTEXT_MENU_HELP = 0; 84 85 @Override onCreate(Bundle savedInstanceState)86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 getWindow().addSystemFlags( 89 WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 90 CatLog.d(LOG_TAG, "onCreate"); 91 92 ActionBar actionBar = getActionBar(); 93 actionBar.setCustomView(R.layout.stk_title); 94 actionBar.setDisplayShowCustomEnabled(true); 95 96 StkApp.setupEdgeToEdge(this); 97 // Set the layout for this activity. 98 setContentView(R.layout.stk_menu_list); 99 mTitleTextView = (TextView) findViewById(R.id.title_text); 100 mTitleIconView = (ImageView) findViewById(R.id.title_icon); 101 mProgressView = (ProgressBar) findViewById(R.id.progress_bar); 102 getListView().setOnCreateContextMenuListener(this); 103 104 // appService can be null if this activity is automatically recreated by the system 105 // with the saved instance state right after the phone process is killed. 106 if (appService == null) { 107 CatLog.d(LOG_TAG, "onCreate - appService is null"); 108 finish(); 109 return; 110 } 111 112 LocalBroadcastManager.getInstance(this).registerReceiver(mLocalBroadcastReceiver, 113 new IntentFilter(StkAppService.SESSION_ENDED)); 114 initFromIntent(getIntent()); 115 if (!SubscriptionManager.isValidSlotIndex(mSlotId)) { 116 finish(); 117 return; 118 } 119 if (mState == STATE_SECONDARY) { 120 appService.getStkContext(mSlotId).setPendingActivityInstance(this); 121 } 122 } 123 124 @Override onListItemClick(ListView l, View v, int position, long id)125 protected void onListItemClick(ListView l, View v, int position, long id) { 126 super.onListItemClick(l, v, position, id); 127 128 if (!mAcceptUsersInput) { 129 CatLog.d(LOG_TAG, "mAcceptUsersInput:false"); 130 return; 131 } 132 133 Item item = getSelectedItem(position); 134 if (item == null) { 135 CatLog.d(LOG_TAG, "Item is null"); 136 return; 137 } 138 139 CatLog.d(LOG_TAG, "onListItemClick Id: " + item.id + ", mState: " + mState); 140 sendResponse(StkAppService.RES_ID_MENU_SELECTION, item.id, false); 141 invalidateOptionsMenu(); 142 } 143 144 @Override onKeyDown(int keyCode, KeyEvent event)145 public boolean onKeyDown(int keyCode, KeyEvent event) { 146 CatLog.d(LOG_TAG, "mAcceptUsersInput: " + mAcceptUsersInput); 147 if (!mAcceptUsersInput) { 148 return true; 149 } 150 151 switch (keyCode) { 152 case KeyEvent.KEYCODE_BACK: 153 CatLog.d(LOG_TAG, "KEYCODE_BACK - mState[" + mState + "]"); 154 switch (mState) { 155 case STATE_SECONDARY: 156 CatLog.d(LOG_TAG, "STATE_SECONDARY"); 157 sendResponse(StkAppService.RES_ID_BACKWARD); 158 return true; 159 case STATE_MAIN: 160 CatLog.d(LOG_TAG, "STATE_MAIN"); 161 finish(); 162 return true; 163 } 164 break; 165 } 166 return super.onKeyDown(keyCode, event); 167 } 168 169 @Override onResume()170 public void onResume() { 171 super.onResume(); 172 173 CatLog.d(LOG_TAG, "onResume, slot id: " + mSlotId + "," + mState); 174 appService.indicateMenuVisibility(true, mSlotId); 175 if (mState == STATE_MAIN) { 176 mStkMenu = appService.getMainMenu(mSlotId); 177 } else { 178 mStkMenu = appService.getMenu(mSlotId); 179 } 180 if (mStkMenu == null) { 181 CatLog.d(LOG_TAG, "menu is null"); 182 cancelTimeOut(); 183 finish(); 184 return; 185 } 186 displayMenu(); 187 188 if (mAlarmTime == NO_SELECT_ALARM) { 189 startTimeOut(); 190 } 191 192 invalidateOptionsMenu(); 193 } 194 195 @Override onPause()196 public void onPause() { 197 super.onPause(); 198 CatLog.d(LOG_TAG, "onPause, slot id: " + mSlotId + "," + mState); 199 //If activity is finished in onResume and it reaults from null appService. 200 if (appService != null) { 201 appService.indicateMenuVisibility(false, mSlotId); 202 } else { 203 CatLog.d(LOG_TAG, "onPause: null appService."); 204 } 205 206 /* 207 * do not cancel the timer here cancelTimeOut(). If any higher/lower 208 * priority events such as incoming call, new sms, screen off intent, 209 * notification alerts, user actions such as 'User moving to another activtiy' 210 * etc.. occur during SELECT ITEM ongoing session, 211 * this activity would receive 'onPause()' event resulting in 212 * cancellation of the timer. As a result no terminal response is 213 * sent to the card. 214 */ 215 216 } 217 218 @Override onStop()219 public void onStop() { 220 super.onStop(); 221 CatLog.d(LOG_TAG, "onStop, slot id: " + mSlotId + "," + mIsResponseSent + "," + mState); 222 } 223 224 @Override onDestroy()225 public void onDestroy() { 226 getListView().setOnCreateContextMenuListener(null); 227 super.onDestroy(); 228 CatLog.d(LOG_TAG, "onDestroy" + ", " + mState); 229 if (appService == null || !SubscriptionManager.isValidSlotIndex(mSlotId)) { 230 return; 231 } 232 //isMenuPending: if input act is finish by stkappservice when OP_LAUNCH_APP again, 233 //we can not send TR here, since the input cmd is waiting user to process. 234 if (mState == STATE_SECONDARY && !mIsResponseSent && !appService.isMenuPending(mSlotId)) { 235 // Avoid sending the terminal response while the activty is being restarted 236 // due to some kind of configuration change. 237 if (!isChangingConfigurations()) { 238 CatLog.d(LOG_TAG, "handleDestroy - Send End Session"); 239 sendResponse(StkAppService.RES_ID_END_SESSION); 240 } 241 } 242 cancelTimeOut(); 243 LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalBroadcastReceiver); 244 } 245 246 @Override onCreateOptionsMenu(android.view.Menu menu)247 public boolean onCreateOptionsMenu(android.view.Menu menu) { 248 super.onCreateOptionsMenu(menu); 249 menu.add(0, StkApp.MENU_ID_END_SESSION, 1, R.string.menu_end_session); 250 return true; 251 } 252 253 @Override onPrepareOptionsMenu(android.view.Menu menu)254 public boolean onPrepareOptionsMenu(android.view.Menu menu) { 255 super.onPrepareOptionsMenu(menu); 256 boolean mainVisible = false; 257 258 if (mState == STATE_SECONDARY && mAcceptUsersInput) { 259 mainVisible = true; 260 } 261 262 menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(mainVisible); 263 264 return mainVisible; 265 } 266 267 @Override onOptionsItemSelected(MenuItem item)268 public boolean onOptionsItemSelected(MenuItem item) { 269 if (!mAcceptUsersInput) { 270 return true; 271 } 272 switch (item.getItemId()) { 273 case StkApp.MENU_ID_END_SESSION: 274 // send session end response. 275 sendResponse(StkAppService.RES_ID_END_SESSION); 276 finish(); 277 return true; 278 default: 279 break; 280 } 281 return super.onOptionsItemSelected(item); 282 } 283 284 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)285 public void onCreateContextMenu(ContextMenu menu, View v, 286 ContextMenuInfo menuInfo) { 287 CatLog.d(LOG_TAG, "onCreateContextMenu"); 288 boolean helpVisible = false; 289 if (mStkMenu != null) { 290 helpVisible = mStkMenu.helpAvailable; 291 } 292 if (helpVisible) { 293 CatLog.d(LOG_TAG, "add menu"); 294 menu.add(0, CONTEXT_MENU_HELP, 0, R.string.help); 295 } 296 } 297 298 @Override onContextItemSelected(MenuItem item)299 public boolean onContextItemSelected(MenuItem item) { 300 AdapterView.AdapterContextMenuInfo info; 301 try { 302 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 303 } catch (ClassCastException e) { 304 return false; 305 } 306 switch (item.getItemId()) { 307 case CONTEXT_MENU_HELP: 308 int position = info.position; 309 CatLog.d(LOG_TAG, "Position:" + position); 310 Item stkItem = getSelectedItem(position); 311 if (stkItem != null) { 312 CatLog.d(LOG_TAG, "item id:" + stkItem.id); 313 sendResponse(StkAppService.RES_ID_MENU_SELECTION, stkItem.id, true); 314 } 315 return true; 316 317 default: 318 return super.onContextItemSelected(item); 319 } 320 } 321 322 @Override onSaveInstanceState(Bundle outState)323 protected void onSaveInstanceState(Bundle outState) { 324 CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId); 325 outState.putInt(STATE_KEY, mState); 326 outState.putBoolean(ACCEPT_USERS_INPUT_KEY, mAcceptUsersInput); 327 outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent); 328 outState.putLong(ALARM_TIME_KEY, mAlarmTime); 329 } 330 331 @Override onRestoreInstanceState(Bundle savedInstanceState)332 protected void onRestoreInstanceState(Bundle savedInstanceState) { 333 CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId); 334 mState = savedInstanceState.getInt(STATE_KEY); 335 mAcceptUsersInput = savedInstanceState.getBoolean(ACCEPT_USERS_INPUT_KEY); 336 if (!mAcceptUsersInput) { 337 // Check the latest information as the saved instance state can be outdated. 338 if ((mState == STATE_MAIN) && appService.isMainMenuAvailable(mSlotId)) { 339 mAcceptUsersInput = true; 340 } else { 341 showProgressBar(true); 342 } 343 } 344 mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY); 345 346 mAlarmTime = savedInstanceState.getLong(ALARM_TIME_KEY, NO_SELECT_ALARM); 347 if (mAlarmTime != NO_SELECT_ALARM) { 348 startTimeOut(); 349 } 350 } 351 cancelTimeOut()352 private void cancelTimeOut() { 353 if (mAlarmTime != NO_SELECT_ALARM) { 354 CatLog.d(LOG_TAG, "cancelTimeOut - slot id: " + mSlotId); 355 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 356 am.cancel(mAlarmListener); 357 mAlarmTime = NO_SELECT_ALARM; 358 } 359 } 360 startTimeOut()361 private void startTimeOut() { 362 // No need to set alarm if this is the main menu or device sent TERMINAL RESPONSE already. 363 if (mState != STATE_SECONDARY || mIsResponseSent) { 364 return; 365 } 366 367 if (mAlarmTime == NO_SELECT_ALARM) { 368 mAlarmTime = SystemClock.elapsedRealtime() + StkApp.UI_TIMEOUT; 369 } 370 371 CatLog.d(LOG_TAG, "startTimeOut: " + mAlarmTime + "ms, slot id: " + mSlotId); 372 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 373 am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mAlarmTime, SELECT_ALARM_TAG, 374 mAlarmListener, null); 375 } 376 377 // Bind list adapter to the items list. displayMenu()378 private void displayMenu() { 379 380 if (mStkMenu != null) { 381 String title = mStkMenu.title == null ? getString(R.string.app_name) : mStkMenu.title; 382 // Display title & title icon 383 if (mStkMenu.titleIcon != null) { 384 mTitleIconView.setImageBitmap(mStkMenu.titleIcon); 385 mTitleIconView.setVisibility(View.VISIBLE); 386 mTitleTextView.setVisibility(View.INVISIBLE); 387 if (!mStkMenu.titleIconSelfExplanatory) { 388 mTitleTextView.setText(title); 389 mTitleTextView.setVisibility(View.VISIBLE); 390 } 391 } else { 392 mTitleIconView.setVisibility(View.GONE); 393 mTitleTextView.setVisibility(View.VISIBLE); 394 mTitleTextView.setText(title); 395 } 396 // create an array adapter for the menu list 397 StkMenuAdapter adapter = new StkMenuAdapter(this, 398 mStkMenu.items, mStkMenu.itemsIconSelfExplanatory); 399 // Bind menu list to the new adapter. 400 setListAdapter(adapter); 401 // Set default item 402 setSelection(mStkMenu.defaultItem); 403 } 404 } 405 showProgressBar(boolean show)406 private void showProgressBar(boolean show) { 407 if (show) { 408 mProgressView.setIndeterminate(true); 409 mProgressView.setVisibility(View.VISIBLE); 410 } else { 411 mProgressView.setIndeterminate(false); 412 mProgressView.setVisibility(View.GONE); 413 } 414 } 415 initFromIntent(Intent intent)416 private void initFromIntent(Intent intent) { 417 418 if (intent != null) { 419 mState = intent.getIntExtra("STATE", STATE_MAIN); 420 mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1); 421 CatLog.d(LOG_TAG, "slot id: " + mSlotId + ", state: " + mState); 422 } else { 423 CatLog.d(LOG_TAG, "finish!"); 424 finish(); 425 } 426 } 427 getSelectedItem(int position)428 private Item getSelectedItem(int position) { 429 Item item = null; 430 if (mStkMenu != null) { 431 try { 432 item = mStkMenu.items.get(position); 433 } catch (IndexOutOfBoundsException e) { 434 if (StkApp.DBG) { 435 CatLog.d(LOG_TAG, "IOOBE Invalid menu"); 436 } 437 } catch (NullPointerException e) { 438 if (StkApp.DBG) { 439 CatLog.d(LOG_TAG, "NPE Invalid menu"); 440 } 441 } 442 } 443 return item; 444 } 445 sendResponse(int resId)446 private void sendResponse(int resId) { 447 sendResponse(resId, 0, false); 448 } 449 sendResponse(int resId, int itemId, boolean help)450 private void sendResponse(int resId, int itemId, boolean help) { 451 CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] itemId[" + itemId + 452 "] help[" + help + "]"); 453 454 // Disallow user operation temporarily until receiving the result of the response. 455 mAcceptUsersInput = false; 456 if (resId == StkAppService.RES_ID_MENU_SELECTION) { 457 showProgressBar(true); 458 } 459 cancelTimeOut(); 460 461 mIsResponseSent = true; 462 Bundle args = new Bundle(); 463 args.putInt(StkAppService.RES_ID, resId); 464 args.putInt(StkAppService.MENU_SELECTION, itemId); 465 args.putBoolean(StkAppService.HELP, help); 466 appService.sendResponse(args, mSlotId); 467 } 468 469 private final BroadcastReceiver mLocalBroadcastReceiver = new BroadcastReceiver() { 470 @Override 471 public void onReceive(Context context, Intent intent) { 472 if (StkAppService.SESSION_ENDED.equals(intent.getAction())) { 473 int slotId = intent.getIntExtra(StkAppService.SLOT_ID, 0); 474 if ((mState == STATE_MAIN) && (mSlotId == slotId)) { 475 mAcceptUsersInput = true; 476 showProgressBar(false); 477 } 478 } 479 } 480 }; 481 482 private final AlarmManager.OnAlarmListener mAlarmListener = 483 new AlarmManager.OnAlarmListener() { 484 @Override 485 public void onAlarm() { 486 CatLog.d(LOG_TAG, "The alarm time is reached"); 487 mAlarmTime = NO_SELECT_ALARM; 488 sendResponse(StkAppService.RES_ID_TIMEOUT); 489 } 490 }; 491 } 492