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