• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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