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