• 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.AlarmManager;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Configuration;
23 import android.os.Bundle;
24 import android.os.SystemClock;
25 import android.telephony.CarrierConfigManager;
26 import android.text.Editable;
27 import android.text.InputFilter;
28 import android.text.TextUtils;
29 import android.text.TextWatcher;
30 import android.text.method.PasswordTransformationMethod;
31 import android.view.KeyEvent;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.WindowManager;
36 import android.view.inputmethod.EditorInfo;
37 import android.widget.Button;
38 import android.widget.EditText;
39 import android.widget.ImageView;
40 import android.widget.PopupMenu;
41 import android.widget.TextView;
42 import android.widget.TextView.BufferType;
43 
44 import androidx.appcompat.app.AppCompatActivity;
45 import androidx.appcompat.widget.Toolbar;
46 
47 import com.android.internal.telephony.cat.CatLog;
48 import com.android.internal.telephony.cat.Input;
49 
50 import com.google.android.material.textfield.TextInputLayout;
51 
52 /**
53  * Display a request for a text input a long with a text edit form.
54  */
55 public class StkInputActivity extends AppCompatActivity implements View.OnClickListener,
56         TextWatcher {
57 
58     // Members
59     private int mState;
60     private EditText mTextIn = null;
61     private TextView mPromptView = null;
62     private View mMoreOptions = null;
63     private PopupMenu mPopupMenu = null;
64     private View mYesNoLayout = null;
65     private View mNormalLayout = null;
66 
67     // Constants
68     private static final String LOG_TAG =
69             new Object(){}.getClass().getEnclosingClass().getSimpleName();
70 
71     private Input mStkInput = null;
72     // Constants
73     private static final int STATE_TEXT = 1;
74     private static final int STATE_YES_NO = 2;
75 
76     static final String YES_STR_RESPONSE = "YES";
77     static final String NO_STR_RESPONSE = "NO";
78 
79     // Font size factor values.
80     static final float NORMAL_FONT_FACTOR = 1;
81     static final float LARGE_FONT_FACTOR = 2;
82     static final float SMALL_FONT_FACTOR = (1 / 2);
83 
84     // Keys for saving the state of the activity in the bundle
85     private static final String RESPONSE_SENT_KEY = "response_sent";
86     private static final String INPUT_STRING_KEY = "input_string";
87     private static final String ALARM_TIME_KEY = "alarm_time";
88 
89     private static final String INPUT_ALARM_TAG = LOG_TAG;
90     private static final long NO_INPUT_ALARM = -1;
91     private long mAlarmTime = NO_INPUT_ALARM;
92 
93     private StkAppService appService = StkAppService.getInstance();
94 
95     private boolean mIsResponseSent = false;
96     private int mSlotId = -1;
97 
98     // Click listener to handle buttons press..
onClick(View v)99     public void onClick(View v) {
100         String input = null;
101         if (mIsResponseSent) {
102             CatLog.d(LOG_TAG, "Already responded");
103             return;
104         }
105 
106         switch (v.getId()) {
107         case R.id.button_ok:
108             input = mTextIn.getText().toString();
109             break;
110         case R.id.button_cancel:
111             sendResponse(StkAppService.RES_ID_END_SESSION);
112             finish();
113             return;
114         // Yes/No layout buttons.
115         case R.id.button_yes:
116             input = YES_STR_RESPONSE;
117             break;
118         case R.id.button_no:
119             input = NO_STR_RESPONSE;
120             break;
121         case R.id.more:
122             if (mPopupMenu == null) {
123                 mPopupMenu = new PopupMenu(this, v);
124                 Menu menu = mPopupMenu.getMenu();
125                 createOptionsMenuInternal(menu);
126                 prepareOptionsMenuInternal(menu);
127                 mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
128                     public boolean onMenuItemClick(MenuItem item) {
129                         optionsItemSelectedInternal(item);
130                         return true;
131                     }
132                 });
133                 mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
134                     public void onDismiss(PopupMenu menu) {
135                         mPopupMenu = null;
136                     }
137                 });
138                 mPopupMenu.show();
139             }
140             return;
141         default:
142             break;
143         }
144         CatLog.d(LOG_TAG, "handleClick, ready to response");
145         sendResponse(StkAppService.RES_ID_INPUT, input, false);
146     }
147 
148     @Override
onCreate(Bundle savedInstanceState)149     public void onCreate(Bundle savedInstanceState) {
150         super.onCreate(savedInstanceState);
151         getWindow().addSystemFlags(
152                 WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
153         CatLog.d(LOG_TAG, "onCreate - mIsResponseSent[" + mIsResponseSent + "]");
154 
155         // appService can be null if this activity is automatically recreated by the system
156         // with the saved instance state right after the phone process is killed.
157         if (appService == null) {
158             CatLog.d(LOG_TAG, "onCreate - appService is null");
159             finish();
160             return;
161         }
162 
163         // Set the layout for this activity.
164         setContentView(R.layout.stk_input);
165         setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
166 
167         if (getResources().getBoolean(R.bool.show_menu_title_only_on_menu)) {
168             getSupportActionBar().hide();
169 
170             mMoreOptions = findViewById(R.id.more);
171             mMoreOptions.setVisibility(View.VISIBLE);
172             mMoreOptions.setOnClickListener(this);
173         }
174 
175         // Initialize members
176         mTextIn = (EditText) this.findViewById(R.id.in_text);
177         mPromptView = (TextView) this.findViewById(R.id.prompt);
178         // Set buttons listeners.
179         Button okButton = (Button) findViewById(R.id.button_ok);
180         Button cancelButton = (Button) findViewById(R.id.button_cancel);
181         Button yesButton = (Button) findViewById(R.id.button_yes);
182         Button noButton = (Button) findViewById(R.id.button_no);
183 
184         okButton.setOnClickListener(this);
185         cancelButton.setOnClickListener(this);
186         yesButton.setOnClickListener(this);
187         noButton.setOnClickListener(this);
188 
189         mYesNoLayout = findViewById(R.id.yes_no_layout);
190         mNormalLayout = findViewById(R.id.normal_layout);
191         initFromIntent(getIntent());
192         appService.getStkContext(mSlotId).setPendingActivityInstance(this);
193     }
194 
195     @Override
onPostCreate(Bundle savedInstanceState)196     protected void onPostCreate(Bundle savedInstanceState) {
197         super.onPostCreate(savedInstanceState);
198 
199         mTextIn.addTextChangedListener(this);
200     }
201 
202     @Override
onResume()203     public void onResume() {
204         super.onResume();
205         CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent +
206                 "], slot id: " + mSlotId);
207         if (mAlarmTime == NO_INPUT_ALARM) {
208             startTimeOut();
209         }
210     }
211 
212     @Override
onPause()213     public void onPause() {
214         super.onPause();
215         CatLog.d(LOG_TAG, "onPause - mIsResponseSent[" + mIsResponseSent + "]");
216         if (mPopupMenu != null) {
217             mPopupMenu.dismiss();
218         }
219     }
220 
221     @Override
onStop()222     public void onStop() {
223         super.onStop();
224         CatLog.d(LOG_TAG, "onStop - mIsResponseSent[" + mIsResponseSent + "]");
225     }
226 
227     @Override
onDestroy()228     public void onDestroy() {
229         super.onDestroy();
230         CatLog.d(LOG_TAG, "onDestroy - before Send End Session mIsResponseSent[" +
231                 mIsResponseSent + " , " + mSlotId + "]");
232         if (appService == null) {
233             return;
234         }
235         // Avoid sending the terminal response while the activty is being restarted
236         // due to some kind of configuration change.
237         if (!isChangingConfigurations()) {
238             // If the input activity is finished by stkappservice
239             // when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here,
240             // since the input cmd is waiting user to process.
241             if (!mIsResponseSent && !appService.isInputPending(mSlotId)) {
242                 CatLog.d(LOG_TAG, "handleDestroy - Send End Session");
243                 sendResponse(StkAppService.RES_ID_END_SESSION);
244             }
245         }
246         cancelTimeOut();
247     }
248 
249     @Override
onConfigurationChanged(Configuration newConfig)250     public void onConfigurationChanged(Configuration newConfig) {
251         super.onConfigurationChanged(newConfig);
252         if (mPopupMenu != null) {
253             mPopupMenu.dismiss();
254         }
255     }
256 
257     @Override
onKeyDown(int keyCode, KeyEvent event)258     public boolean onKeyDown(int keyCode, KeyEvent event) {
259         if (mIsResponseSent) {
260             CatLog.d(LOG_TAG, "Already responded");
261             return true;
262         }
263 
264         switch (keyCode) {
265         case KeyEvent.KEYCODE_BACK:
266             CatLog.d(LOG_TAG, "onKeyDown - KEYCODE_BACK");
267             sendResponse(StkAppService.RES_ID_BACKWARD, null, false);
268             return true;
269         }
270         return super.onKeyDown(keyCode, event);
271     }
272 
sendResponse(int resId)273     void sendResponse(int resId) {
274         sendResponse(resId, null, false);
275     }
276 
sendResponse(int resId, String input, boolean help)277     void sendResponse(int resId, String input, boolean help) {
278         cancelTimeOut();
279 
280         if (mSlotId == -1) {
281             CatLog.d(LOG_TAG, "slot id is invalid");
282             return;
283         }
284 
285         if (StkAppService.getInstance() == null) {
286             CatLog.d(LOG_TAG, "StkAppService is null, Ignore response: id is " + resId);
287             return;
288         }
289 
290         if (mMoreOptions != null) {
291             mMoreOptions.setVisibility(View.INVISIBLE);
292         }
293 
294         CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] input[*****] help["
295                 + help + "]");
296         mIsResponseSent = true;
297         Bundle args = new Bundle();
298         args.putInt(StkAppService.RES_ID, resId);
299         if (input != null) {
300             args.putString(StkAppService.INPUT, input);
301         }
302         args.putBoolean(StkAppService.HELP, help);
303         appService.sendResponse(args, mSlotId);
304     }
305 
306     @Override
onCreateOptionsMenu(android.view.Menu menu)307     public boolean onCreateOptionsMenu(android.view.Menu menu) {
308         super.onCreateOptionsMenu(menu);
309         createOptionsMenuInternal(menu);
310         return true;
311     }
312 
createOptionsMenuInternal(Menu menu)313     private void createOptionsMenuInternal(Menu menu) {
314         menu.add(Menu.NONE, StkApp.MENU_ID_END_SESSION, 1, R.string.menu_end_session);
315         menu.add(0, StkApp.MENU_ID_HELP, 2, R.string.help);
316     }
317 
318     @Override
onPrepareOptionsMenu(android.view.Menu menu)319     public boolean onPrepareOptionsMenu(android.view.Menu menu) {
320         super.onPrepareOptionsMenu(menu);
321         prepareOptionsMenuInternal(menu);
322         return true;
323     }
324 
prepareOptionsMenuInternal(Menu menu)325     private void prepareOptionsMenuInternal(Menu menu) {
326         menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(true);
327         menu.findItem(StkApp.MENU_ID_HELP).setVisible(mStkInput.helpAvailable);
328     }
329 
330     @Override
onOptionsItemSelected(MenuItem item)331     public boolean onOptionsItemSelected(MenuItem item) {
332         if (optionsItemSelectedInternal(item)) {
333             return true;
334         }
335         return super.onOptionsItemSelected(item);
336     }
337 
optionsItemSelectedInternal(MenuItem item)338     private boolean optionsItemSelectedInternal(MenuItem item) {
339         if (mIsResponseSent) {
340             CatLog.d(LOG_TAG, "Already responded");
341             return true;
342         }
343         switch (item.getItemId()) {
344         case StkApp.MENU_ID_END_SESSION:
345             sendResponse(StkAppService.RES_ID_END_SESSION);
346             finish();
347             return true;
348         case StkApp.MENU_ID_HELP:
349             sendResponse(StkAppService.RES_ID_INPUT, "", true);
350             return true;
351         }
352         return false;
353     }
354 
355     @Override
onSaveInstanceState(Bundle outState)356     protected void onSaveInstanceState(Bundle outState) {
357         CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId);
358         outState.putBoolean(RESPONSE_SENT_KEY, mIsResponseSent);
359         outState.putString(INPUT_STRING_KEY, mTextIn.getText().toString());
360         outState.putLong(ALARM_TIME_KEY, mAlarmTime);
361     }
362 
363     @Override
onRestoreInstanceState(Bundle savedInstanceState)364     protected void onRestoreInstanceState(Bundle savedInstanceState) {
365         CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId);
366 
367         mIsResponseSent = savedInstanceState.getBoolean(RESPONSE_SENT_KEY);
368         if (mIsResponseSent && (mMoreOptions != null)) {
369             mMoreOptions.setVisibility(View.INVISIBLE);
370         }
371 
372         String savedString = savedInstanceState.getString(INPUT_STRING_KEY);
373         mTextIn.setText(savedString);
374         updateButton();
375 
376         mAlarmTime = savedInstanceState.getLong(ALARM_TIME_KEY, NO_INPUT_ALARM);
377         if (mAlarmTime != NO_INPUT_ALARM) {
378             startTimeOut();
379         }
380     }
381 
beforeTextChanged(CharSequence s, int start, int count, int after)382     public void beforeTextChanged(CharSequence s, int start, int count,
383             int after) {
384     }
385 
onTextChanged(CharSequence s, int start, int before, int count)386     public void onTextChanged(CharSequence s, int start, int before, int count) {
387         // Reset timeout.
388         cancelTimeOut();
389         startTimeOut();
390         updateButton();
391     }
392 
afterTextChanged(Editable s)393     public void afterTextChanged(Editable s) {
394     }
395 
updateButton()396     private void updateButton() {
397         // Disable the button if the length of the input text does not meet the expectation.
398         Button okButton = (Button) findViewById(R.id.button_ok);
399         okButton.setEnabled((mTextIn.getText().length() < mStkInput.minLen) ? false : true);
400     }
401 
402     private void cancelTimeOut() {
403         if (mAlarmTime != NO_INPUT_ALARM) {
404             CatLog.d(LOG_TAG, "cancelTimeOut - slot id: " + mSlotId);
405             AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
406             am.cancel(mAlarmListener);
407             mAlarmTime = NO_INPUT_ALARM;
408         }
409     }
410 
411     private void startTimeOut() {
412         // No need to set alarm if device sent TERMINAL RESPONSE already.
413         if (mIsResponseSent) {
414             return;
415         }
416 
417         if (mAlarmTime == NO_INPUT_ALARM) {
418             int duration = StkApp.calculateDurationInMilis(mStkInput.duration);
419             if (duration <= 0) {
420                 duration = StkApp.UI_TIMEOUT;
421             }
422             mAlarmTime = SystemClock.elapsedRealtime() + duration;
423         }
424 
425         CatLog.d(LOG_TAG, "startTimeOut: " + mAlarmTime + "ms, slot id: " + mSlotId);
426         AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
427         am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mAlarmTime, INPUT_ALARM_TAG,
428                 mAlarmListener, null);
429     }
430 
431     private void configInputDisplay() {
432         TextInputLayout textInput = (TextInputLayout) findViewById(R.id.text_input_layout);
433 
434         int inTypeId = R.string.alphabet;
435 
436         // set the prompt.
437         if ((mStkInput.icon == null || !mStkInput.iconSelfExplanatory)
438                 && !TextUtils.isEmpty(mStkInput.text)) {
439             mPromptView.setText(mStkInput.text);
440             mPromptView.setVisibility(View.VISIBLE);
441         }
442 
443         // Set input type (alphabet/digit) info close to the InText form.
444         boolean hideHelper = false;
445         if (mStkInput.digitOnly) {
446             mTextIn.setKeyListener(StkDigitsKeyListener.getInstance());
447             inTypeId = R.string.digits;
448             hideHelper = StkAppService.getBooleanCarrierConfig(this,
449                     CarrierConfigManager.KEY_HIDE_DIGITS_HELPER_TEXT_ON_STK_INPUT_SCREEN_BOOL,
450                     mSlotId);
451         }
452         textInput.setHelperText(getResources().getString(inTypeId));
453         textInput.setHelperTextEnabled(!hideHelper);
454         CatLog.d(LOG_TAG,
455                 String.format("configInputDisplay: digitOnly=%s, hideHelper=%s",
456                         mStkInput.digitOnly, hideHelper));
457         setTitle(R.string.app_name);
458 
459         if (mStkInput.icon != null) {
460             ImageView imageView = (ImageView) findViewById(R.id.icon);
461             imageView.setImageBitmap(mStkInput.icon);
462             imageView.setVisibility(View.VISIBLE);
463         }
464 
465         // Handle specific global and text attributes.
466         switch (mState) {
467         case STATE_TEXT:
468             mTextIn.setFilters(new InputFilter[] {new InputFilter.LengthFilter(mStkInput.maxLen)});
469 
470             textInput.setCounterMaxLength(mStkInput.maxLen);
471             //do not show the length helper for the text input
472             textInput.setCounterEnabled(false);
473 
474             if (!mStkInput.echo) {
475                 mTextIn.setTransformationMethod(PasswordTransformationMethod
476                         .getInstance());
477             }
478             mTextIn.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN);
479             // Request the initial focus on the edit box and show the software keyboard.
480             mTextIn.requestFocus();
481             getWindow().setSoftInputMode(
482                     WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
483             // Set default text if present.
484             if (mStkInput.defaultText != null) {
485                 mTextIn.setText(mStkInput.defaultText);
486             } else {
487                 // make sure the text is cleared
488                 mTextIn.setText("", BufferType.EDITABLE);
489             }
490             updateButton();
491 
492             break;
493         case STATE_YES_NO:
494             // Set display mode - normal / yes-no layout
495             mYesNoLayout.setVisibility(View.VISIBLE);
496             mNormalLayout.setVisibility(View.GONE);
497             break;
498         }
499     }
500 
501     private void initFromIntent(Intent intent) {
502         // Get the calling intent type: text/key, and setup the
503         // display parameters.
504         CatLog.d(LOG_TAG, "initFromIntent - slot id: " + mSlotId);
505         if (intent != null) {
506             mStkInput = intent.getParcelableExtra("INPUT");
507             mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1);
508             CatLog.d(LOG_TAG, "onCreate - slot id: " + mSlotId);
509             if (mStkInput == null) {
510                 finish();
511             } else {
512                 mState = mStkInput.yesNo ? STATE_YES_NO :
513                         STATE_TEXT;
514                 configInputDisplay();
515             }
516         } else {
517             finish();
518         }
519     }
520 
521     private final AlarmManager.OnAlarmListener mAlarmListener =
522             new AlarmManager.OnAlarmListener() {
523                 @Override
524                 public void onAlarm() {
525                     CatLog.d(LOG_TAG, "The alarm time is reached");
526                     mAlarmTime = NO_INPUT_ALARM;
527                     sendResponse(StkAppService.RES_ID_TIMEOUT);
528                 }
529             };
530 }
531