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 // Set the layout for this activity. 97 setContentView(R.layout.stk_menu_list); 98 mTitleTextView = (TextView) findViewById(R.id.title_text); 99 mTitleIconView = (ImageView) findViewById(R.id.title_icon); 100 mProgressView = (ProgressBar) findViewById(R.id.progress_bar); 101 getListView().setOnCreateContextMenuListener(this); 102 103 // appService can be null if this activity is automatically recreated by the system 104 // with the saved instance state right after the phone process is killed. 105 if (appService == null) { 106 CatLog.d(LOG_TAG, "onCreate - appService is null"); 107 finish(); 108 return; 109 } 110 111 LocalBroadcastManager.getInstance(this).registerReceiver(mLocalBroadcastReceiver, 112 new IntentFilter(StkAppService.SESSION_ENDED)); 113 initFromIntent(getIntent()); 114 if (!SubscriptionManager.isValidSlotIndex(mSlotId)) { 115 finish(); 116 return; 117 } 118 if (mState == STATE_SECONDARY) { 119 appService.getStkContext(mSlotId).setPendingActivityInstance(this); 120 } 121 } 122 123 @Override onListItemClick(ListView l, View v, int position, long id)124 protected void onListItemClick(ListView l, View v, int position, long id) { 125 super.onListItemClick(l, v, position, id); 126 127 if (!mAcceptUsersInput) { 128 CatLog.d(LOG_TAG, "mAcceptUsersInput:false"); 129 return; 130 } 131 132 Item item = getSelectedItem(position); 133 if (item == null) { 134 CatLog.d(LOG_TAG, "Item is null"); 135 return; 136 } 137 138 CatLog.d(LOG_TAG, "onListItemClick Id: " + item.id + ", mState: " + mState); 139 sendResponse(StkAppService.RES_ID_MENU_SELECTION, item.id, false); 140 invalidateOptionsMenu(); 141 } 142 143 @Override onKeyDown(int keyCode, KeyEvent event)144 public boolean onKeyDown(int keyCode, KeyEvent event) { 145 CatLog.d(LOG_TAG, "mAcceptUsersInput: " + mAcceptUsersInput); 146 if (!mAcceptUsersInput) { 147 return true; 148 } 149 150 switch (keyCode) { 151 case KeyEvent.KEYCODE_BACK: 152 CatLog.d(LOG_TAG, "KEYCODE_BACK - mState[" + mState + "]"); 153 switch (mState) { 154 case STATE_SECONDARY: 155 CatLog.d(LOG_TAG, "STATE_SECONDARY"); 156 sendResponse(StkAppService.RES_ID_BACKWARD); 157 return true; 158 case STATE_MAIN: 159 CatLog.d(LOG_TAG, "STATE_MAIN"); 160 finish(); 161 return true; 162 } 163 break; 164 } 165 return super.onKeyDown(keyCode, event); 166 } 167 168 @Override onResume()169 public void onResume() { 170 super.onResume(); 171 172 CatLog.d(LOG_TAG, "onResume, slot id: " + mSlotId + "," + mState); 173 appService.indicateMenuVisibility(true, mSlotId); 174 if (mState == STATE_MAIN) { 175 mStkMenu = appService.getMainMenu(mSlotId); 176 } else { 177 mStkMenu = appService.getMenu(mSlotId); 178 } 179 if (mStkMenu == null) { 180 CatLog.d(LOG_TAG, "menu is null"); 181 cancelTimeOut(); 182 finish(); 183 return; 184 } 185 displayMenu(); 186 187 if (mAlarmTime == NO_SELECT_ALARM) { 188 startTimeOut(); 189 } 190 191 invalidateOptionsMenu(); 192 } 193 194 @Override onPause()195 public void onPause() { 196 super.onPause(); 197 CatLog.d(LOG_TAG, "onPause, slot id: " + mSlotId + "," + mState); 198 //If activity is finished in onResume and it reaults from null appService. 199 if (appService != null) { 200 appService.indicateMenuVisibility(false, mSlotId); 201 } else { 202 CatLog.d(LOG_TAG, "onPause: null appService."); 203 } 204 205 /* 206 * do not cancel the timer here cancelTimeOut(). If any higher/lower 207 * priority events such as incoming call, new sms, screen off intent, 208 * notification alerts, user actions such as 'User moving to another activtiy' 209 * etc.. occur during SELECT ITEM ongoing session, 210 * this activity would receive 'onPause()' event resulting in 211 * cancellation of the timer. As a result no terminal response is 212 * sent to the card. 213 */ 214 215 } 216 217 @Override onStop()218 public void onStop() { 219 super.onStop(); 220 CatLog.d(LOG_TAG, "onStop, slot id: " + mSlotId + "," + mIsResponseSent + "," + mState); 221 } 222 223 @Override onDestroy()224 public void onDestroy() { 225 getListView().setOnCreateContextMenuListener(null); 226 super.onDestroy(); 227 CatLog.d(LOG_TAG, "onDestroy" + ", " + mState); 228 if (appService == null || !SubscriptionManager.isValidSlotIndex(mSlotId)) { 229 return; 230 } 231 //isMenuPending: if input act is finish by stkappservice when OP_LAUNCH_APP again, 232 //we can not send TR here, since the input cmd is waiting user to process. 233 if (mState == STATE_SECONDARY && !mIsResponseSent && !appService.isMenuPending(mSlotId)) { 234 // Avoid sending the terminal response while the activty is being restarted 235 // due to some kind of configuration change. 236 if (!isChangingConfigurations()) { 237 CatLog.d(LOG_TAG, "handleDestroy - Send End Session"); 238 sendResponse(StkAppService.RES_ID_END_SESSION); 239 } 240 } 241 cancelTimeOut(); 242 LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalBroadcastReceiver); 243 } 244 245 @Override onCreateOptionsMenu(android.view.Menu menu)246 public boolean onCreateOptionsMenu(android.view.Menu menu) { 247 super.onCreateOptionsMenu(menu); 248 menu.add(0, StkApp.MENU_ID_END_SESSION, 1, R.string.menu_end_session); 249 return true; 250 } 251 252 @Override onPrepareOptionsMenu(android.view.Menu menu)253 public boolean onPrepareOptionsMenu(android.view.Menu menu) { 254 super.onPrepareOptionsMenu(menu); 255 boolean mainVisible = false; 256 257 if (mState == STATE_SECONDARY && mAcceptUsersInput) { 258 mainVisible = true; 259 } 260 261 menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(mainVisible); 262 263 return mainVisible; 264 } 265 266 @Override onOptionsItemSelected(MenuItem item)267 public boolean onOptionsItemSelected(MenuItem item) { 268 if (!mAcceptUsersInput) { 269 return true; 270 } 271 switch (item.getItemId()) { 272 case StkApp.MENU_ID_END_SESSION: 273 // send session end response. 274 sendResponse(StkAppService.RES_ID_END_SESSION); 275 finish(); 276 return true; 277 default: 278 break; 279 } 280 return super.onOptionsItemSelected(item); 281 } 282 283 @Override onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)284 public void onCreateContextMenu(ContextMenu menu, View v, 285 ContextMenuInfo menuInfo) { 286 CatLog.d(LOG_TAG, "onCreateContextMenu"); 287 boolean helpVisible = false; 288 if (mStkMenu != null) { 289 helpVisible = mStkMenu.helpAvailable; 290 } 291 if (helpVisible) { 292 CatLog.d(LOG_TAG, "add menu"); 293 menu.add(0, CONTEXT_MENU_HELP, 0, R.string.help); 294 } 295 } 296 297 @Override onContextItemSelected(MenuItem item)298 public boolean onContextItemSelected(MenuItem item) { 299 AdapterView.AdapterContextMenuInfo info; 300 try { 301 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 302 } catch (ClassCastException e) { 303 return false; 304 } 305 switch (item.getItemId()) { 306 case CONTEXT_MENU_HELP: 307 int position = info.position; 308 CatLog.d(LOG_TAG, "Position:" + position); 309 Item stkItem = getSelectedItem(position); 310 if (stkItem != null) { 311 CatLog.d(LOG_TAG, "item id:" + stkItem.id); 312 sendResponse(StkAppService.RES_ID_MENU_SELECTION, stkItem.id, true); 313 } 314 return true; 315 316 default: 317 return super.onContextItemSelected(item); 318 } 319 } 320 321 @Override onSaveInstanceState(Bundle outState)322 protected void onSaveInstanceState(Bundle outState) { 323 CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId); 324 outState.putInt(STATE_KEY, mState); 325 outState.putBoolean(ACCEPT_USERS_INPUT_KEY, mAcceptUsersInput); 326 outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent); 327 outState.putLong(ALARM_TIME_KEY, mAlarmTime); 328 } 329 330 @Override onRestoreInstanceState(Bundle savedInstanceState)331 protected void onRestoreInstanceState(Bundle savedInstanceState) { 332 CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId); 333 mState = savedInstanceState.getInt(STATE_KEY); 334 mAcceptUsersInput = savedInstanceState.getBoolean(ACCEPT_USERS_INPUT_KEY); 335 if (!mAcceptUsersInput) { 336 // Check the latest information as the saved instance state can be outdated. 337 if ((mState == STATE_MAIN) && appService.isMainMenuAvailable(mSlotId)) { 338 mAcceptUsersInput = true; 339 } else { 340 showProgressBar(true); 341 } 342 } 343 mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY); 344 345 mAlarmTime = savedInstanceState.getLong(ALARM_TIME_KEY, NO_SELECT_ALARM); 346 if (mAlarmTime != NO_SELECT_ALARM) { 347 startTimeOut(); 348 } 349 } 350 cancelTimeOut()351 private void cancelTimeOut() { 352 if (mAlarmTime != NO_SELECT_ALARM) { 353 CatLog.d(LOG_TAG, "cancelTimeOut - slot id: " + mSlotId); 354 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 355 am.cancel(mAlarmListener); 356 mAlarmTime = NO_SELECT_ALARM; 357 } 358 } 359 startTimeOut()360 private void startTimeOut() { 361 // No need to set alarm if this is the main menu or device sent TERMINAL RESPONSE already. 362 if (mState != STATE_SECONDARY || mIsResponseSent) { 363 return; 364 } 365 366 if (mAlarmTime == NO_SELECT_ALARM) { 367 mAlarmTime = SystemClock.elapsedRealtime() + StkApp.UI_TIMEOUT; 368 } 369 370 CatLog.d(LOG_TAG, "startTimeOut: " + mAlarmTime + "ms, slot id: " + mSlotId); 371 AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 372 am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mAlarmTime, SELECT_ALARM_TAG, 373 mAlarmListener, null); 374 } 375 376 // Bind list adapter to the items list. displayMenu()377 private void displayMenu() { 378 379 if (mStkMenu != null) { 380 String title = mStkMenu.title == null ? getString(R.string.app_name) : mStkMenu.title; 381 // Display title & title icon 382 if (mStkMenu.titleIcon != null) { 383 mTitleIconView.setImageBitmap(mStkMenu.titleIcon); 384 mTitleIconView.setVisibility(View.VISIBLE); 385 mTitleTextView.setVisibility(View.INVISIBLE); 386 if (!mStkMenu.titleIconSelfExplanatory) { 387 mTitleTextView.setText(title); 388 mTitleTextView.setVisibility(View.VISIBLE); 389 } 390 } else { 391 mTitleIconView.setVisibility(View.GONE); 392 mTitleTextView.setVisibility(View.VISIBLE); 393 mTitleTextView.setText(title); 394 } 395 // create an array adapter for the menu list 396 StkMenuAdapter adapter = new StkMenuAdapter(this, 397 mStkMenu.items, mStkMenu.itemsIconSelfExplanatory); 398 // Bind menu list to the new adapter. 399 setListAdapter(adapter); 400 // Set default item 401 setSelection(mStkMenu.defaultItem); 402 } 403 } 404 showProgressBar(boolean show)405 private void showProgressBar(boolean show) { 406 if (show) { 407 mProgressView.setIndeterminate(true); 408 mProgressView.setVisibility(View.VISIBLE); 409 } else { 410 mProgressView.setIndeterminate(false); 411 mProgressView.setVisibility(View.GONE); 412 } 413 } 414 initFromIntent(Intent intent)415 private void initFromIntent(Intent intent) { 416 417 if (intent != null) { 418 mState = intent.getIntExtra("STATE", STATE_MAIN); 419 mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1); 420 CatLog.d(LOG_TAG, "slot id: " + mSlotId + ", state: " + mState); 421 } else { 422 CatLog.d(LOG_TAG, "finish!"); 423 finish(); 424 } 425 } 426 getSelectedItem(int position)427 private Item getSelectedItem(int position) { 428 Item item = null; 429 if (mStkMenu != null) { 430 try { 431 item = mStkMenu.items.get(position); 432 } catch (IndexOutOfBoundsException e) { 433 if (StkApp.DBG) { 434 CatLog.d(LOG_TAG, "IOOBE Invalid menu"); 435 } 436 } catch (NullPointerException e) { 437 if (StkApp.DBG) { 438 CatLog.d(LOG_TAG, "NPE Invalid menu"); 439 } 440 } 441 } 442 return item; 443 } 444 sendResponse(int resId)445 private void sendResponse(int resId) { 446 sendResponse(resId, 0, false); 447 } 448 sendResponse(int resId, int itemId, boolean help)449 private void sendResponse(int resId, int itemId, boolean help) { 450 CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] itemId[" + itemId + 451 "] help[" + help + "]"); 452 453 // Disallow user operation temporarily until receiving the result of the response. 454 mAcceptUsersInput = false; 455 if (resId == StkAppService.RES_ID_MENU_SELECTION) { 456 showProgressBar(true); 457 } 458 cancelTimeOut(); 459 460 mIsResponseSent = true; 461 Bundle args = new Bundle(); 462 args.putInt(StkAppService.RES_ID, resId); 463 args.putInt(StkAppService.MENU_SELECTION, itemId); 464 args.putBoolean(StkAppService.HELP, help); 465 appService.sendResponse(args, mSlotId); 466 } 467 468 private final BroadcastReceiver mLocalBroadcastReceiver = new BroadcastReceiver() { 469 @Override 470 public void onReceive(Context context, Intent intent) { 471 if (StkAppService.SESSION_ENDED.equals(intent.getAction())) { 472 int slotId = intent.getIntExtra(StkAppService.SLOT_ID, 0); 473 if ((mState == STATE_MAIN) && (mSlotId == slotId)) { 474 mAcceptUsersInput = true; 475 showProgressBar(false); 476 } 477 } 478 } 479 }; 480 481 private final AlarmManager.OnAlarmListener mAlarmListener = 482 new AlarmManager.OnAlarmListener() { 483 @Override 484 public void onAlarm() { 485 CatLog.d(LOG_TAG, "The alarm time is reached"); 486 mAlarmTime = NO_SELECT_ALARM; 487 sendResponse(StkAppService.RES_ID_TIMEOUT); 488 } 489 }; 490 } 491