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
onCreate(Bundle icicle)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
onListItemClick(ListView l, View v, int position, long id)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
onKeyDown(int keyCode, KeyEvent event)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
onRestart()189     public void onRestart() {
190         super.onRestart();
191         CatLog.d(LOG_TAG, "onRestart, slot id: " + mSlotId);
192     }
193 
194     @Override
onResume()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
onPause()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
onStop()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
onDestroy()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
onCreateOptionsMenu(android.view.Menu menu)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
onPrepareOptionsMenu(android.view.Menu menu)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
onOptionsItemSelected(MenuItem item)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
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)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
onContextItemSelected(MenuItem item)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
onSaveInstanceState(Bundle outState)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
onRestoreInstanceState(Bundle savedInstanceState)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 
cancelTimeOut()407     private void cancelTimeOut() {
408         CatLog.d(LOG_TAG, "cancelTimeOut: " + mSlotId);
409         mTimeoutHandler.removeMessages(MSG_ID_TIMEOUT);
410     }
411 
startTimeOut()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.
displayMenu()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 
initFromIntent(Intent intent)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 
getSelectedItem(int position)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 
sendResponse(int resId)481     private void sendResponse(int resId) {
482         sendResponse(resId, 0, false);
483     }
484 
sendResponse(int resId, int itemId, boolean help)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