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