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