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