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