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