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