1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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.content.Context;
22 import android.content.Intent;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.text.Editable;
28 import android.text.InputFilter;
29 import android.text.InputType;
30 import android.text.TextWatcher;
31 import android.text.method.PasswordTransformationMethod;
32 import android.view.KeyEvent;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.Window;
36 import android.widget.Button;
37 import android.widget.ImageView;
38 import android.widget.TextView;
39 import android.widget.EditText;
40 import android.widget.TextView.BufferType;
41 import com.android.internal.telephony.cat.CatLog;
42 import com.android.internal.telephony.cat.FontSize;
43 import com.android.internal.telephony.cat.Input;
44 
45 /**
46  * Display a request for a text input a long with a text edit form.
47  */
48 public class StkInputActivity extends Activity implements View.OnClickListener,
49         TextWatcher {
50 
51     // Members
52     private int mState;
53     private Context mContext;
54     private EditText mTextIn = null;
55     private TextView mPromptView = null;
56     private View mYesNoLayout = null;
57     private View mNormalLayout = null;
58 
59     // Constants
60     private static final String className = new Object(){}.getClass().getEnclosingClass().getName();
61     private static final String LOG_TAG = className.substring(className.lastIndexOf('.') + 1);
62 
63     private Input mStkInput = null;
64     private boolean mAcceptUsersInput = true;
65     // Constants
66     private static final int STATE_TEXT = 1;
67     private static final int STATE_YES_NO = 2;
68 
69     static final String YES_STR_RESPONSE = "YES";
70     static final String NO_STR_RESPONSE = "NO";
71 
72     // Font size factor values.
73     static final float NORMAL_FONT_FACTOR = 1;
74     static final float LARGE_FONT_FACTOR = 2;
75     static final float SMALL_FONT_FACTOR = (1 / 2);
76 
77     // message id for time out
78     private static final int MSG_ID_TIMEOUT = 1;
79     private StkAppService appService = StkAppService.getInstance();
80 
81     private boolean mIsResponseSent = false;
82     private int mSlotId = -1;
83     Activity mInstance = null;
84 
85     Handler mTimeoutHandler = new Handler() {
86         @Override
87         public void handleMessage(Message msg) {
88             switch(msg.what) {
89             case MSG_ID_TIMEOUT:
90                 CatLog.d(LOG_TAG, "Msg timeout.");
91                 mAcceptUsersInput = false;
92                 appService.getStkContext(mSlotId).setPendingActivityInstance(mInstance);
93                 sendResponse(StkAppService.RES_ID_TIMEOUT);
94                 break;
95             }
96         }
97     };
98 
99     // Click listener to handle buttons press..
onClick(View v)100     public void onClick(View v) {
101         String input = null;
102         if (!mAcceptUsersInput) {
103             CatLog.d(LOG_TAG, "mAcceptUsersInput:false");
104             return;
105         }
106 
107         switch (v.getId()) {
108         case R.id.button_ok:
109             // Check that text entered is valid .
110             if (!verfiyTypedText()) {
111                 CatLog.d(LOG_TAG, "handleClick, invalid text");
112                 return;
113             }
114             mAcceptUsersInput = false;
115             input = mTextIn.getText().toString();
116             break;
117         // Yes/No layout buttons.
118         case R.id.button_yes:
119             mAcceptUsersInput = false;
120             input = YES_STR_RESPONSE;
121             break;
122         case R.id.button_no:
123             mAcceptUsersInput = false;
124             input = NO_STR_RESPONSE;
125             break;
126         }
127         CatLog.d(LOG_TAG, "handleClick, ready to response");
128         cancelTimeOut();
129         appService.getStkContext(mSlotId).setPendingActivityInstance(this);
130         sendResponse(StkAppService.RES_ID_INPUT, input, false);
131     }
132 
133     @Override
onCreate(Bundle icicle)134     public void onCreate(Bundle icicle) {
135         super.onCreate(icicle);
136 
137         CatLog.d(LOG_TAG, "onCreate - mIsResponseSent[" + mIsResponseSent + "]");
138 
139         // appService can be null if this activity is automatically recreated by the system
140         // with the saved instance state right after the phone process is killed.
141         if (appService == null) {
142             CatLog.d(LOG_TAG, "onCreate - appService is null");
143             finish();
144             return;
145         }
146 
147         ActionBar actionBar = getActionBar();
148         actionBar.setCustomView(R.layout.stk_title);
149         actionBar.setDisplayShowCustomEnabled(true);
150 
151         // Set the layout for this activity.
152         setContentView(R.layout.stk_input);
153 
154         // Initialize members
155         mTextIn = (EditText) this.findViewById(R.id.in_text);
156         mPromptView = (TextView) this.findViewById(R.id.prompt);
157         mInstance = this;
158         // Set buttons listeners.
159         Button okButton = (Button) findViewById(R.id.button_ok);
160         Button yesButton = (Button) findViewById(R.id.button_yes);
161         Button noButton = (Button) findViewById(R.id.button_no);
162 
163         okButton.setOnClickListener(this);
164         yesButton.setOnClickListener(this);
165         noButton.setOnClickListener(this);
166 
167         mYesNoLayout = findViewById(R.id.yes_no_layout);
168         mNormalLayout = findViewById(R.id.normal_layout);
169         initFromIntent(getIntent());
170         mContext = getBaseContext();
171         mAcceptUsersInput = true;
172     }
173 
174     @Override
onPostCreate(Bundle savedInstanceState)175     protected void onPostCreate(Bundle savedInstanceState) {
176         super.onPostCreate(savedInstanceState);
177 
178         mTextIn.addTextChangedListener(this);
179     }
180 
181     @Override
onResume()182     public void onResume() {
183         super.onResume();
184         CatLog.d(LOG_TAG, "onResume - mIsResponseSent[" + mIsResponseSent +
185                 "], slot id: " + mSlotId);
186         startTimeOut();
187         appService.getStkContext(mSlotId).setPendingActivityInstance(null);
188         if (mIsResponseSent) {
189             cancelTimeOut();
190             finish();
191         }
192     }
193 
194     @Override
onPause()195     public void onPause() {
196         super.onPause();
197         CatLog.d(LOG_TAG, "onPause - mIsResponseSent[" + mIsResponseSent + "]");
198     }
199 
200     @Override
onStop()201     public void onStop() {
202         super.onStop();
203         CatLog.d(LOG_TAG, "onStop - mIsResponseSent[" + mIsResponseSent + "]");
204         if (mIsResponseSent) {
205             cancelTimeOut();
206             finish();
207         } else {
208             appService.getStkContext(mSlotId).setPendingActivityInstance(this);
209         }
210     }
211 
212     @Override
onDestroy()213     public void onDestroy() {
214         super.onDestroy();
215         CatLog.d(LOG_TAG, "onDestroy - before Send End Session mIsResponseSent[" +
216                 mIsResponseSent + " , " + mSlotId + "]");
217         if (appService == null) {
218             return;
219         }
220         //If the input activity is finished by stkappservice
221         //when receiving OP_LAUNCH_APP from the other SIM, we can not send TR here
222         //, since the input cmd is waiting user to process.
223         if (!mIsResponseSent && !appService.isInputPending(mSlotId)) {
224             CatLog.d(LOG_TAG, "handleDestroy - Send End Session");
225             sendResponse(StkAppService.RES_ID_END_SESSION);
226         }
227         cancelTimeOut();
228     }
229 
230     @Override
onKeyDown(int keyCode, KeyEvent event)231     public boolean onKeyDown(int keyCode, KeyEvent event) {
232         if (!mAcceptUsersInput) {
233             CatLog.d(LOG_TAG, "mAcceptUsersInput:false");
234             return true;
235         }
236 
237         switch (keyCode) {
238         case KeyEvent.KEYCODE_BACK:
239             CatLog.d(LOG_TAG, "onKeyDown - KEYCODE_BACK");
240             mAcceptUsersInput = false;
241             cancelTimeOut();
242             appService.getStkContext(mSlotId).setPendingActivityInstance(this);
243             sendResponse(StkAppService.RES_ID_BACKWARD, null, false);
244             return true;
245         }
246         return super.onKeyDown(keyCode, event);
247     }
248 
sendResponse(int resId)249     void sendResponse(int resId) {
250         sendResponse(resId, null, false);
251     }
252 
sendResponse(int resId, String input, boolean help)253     void sendResponse(int resId, String input, boolean help) {
254         if (mSlotId == -1) {
255             CatLog.d(LOG_TAG, "slot id is invalid");
256             return;
257         }
258 
259         if (StkAppService.getInstance() == null) {
260             CatLog.d(LOG_TAG, "StkAppService is null, Ignore response: id is " + resId);
261             return;
262         }
263 
264         CatLog.d(LOG_TAG, "sendResponse resID[" + resId + "] input[*****] help["
265                 + help + "]");
266         mIsResponseSent = true;
267         Bundle args = new Bundle();
268         args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE);
269         args.putInt(StkAppService.SLOT_ID, mSlotId);
270         args.putInt(StkAppService.RES_ID, resId);
271         if (input != null) {
272             args.putString(StkAppService.INPUT, input);
273         }
274         args.putBoolean(StkAppService.HELP, help);
275         mContext.startService(new Intent(mContext, StkAppService.class)
276                 .putExtras(args));
277     }
278 
279     @Override
onCreateOptionsMenu(android.view.Menu menu)280     public boolean onCreateOptionsMenu(android.view.Menu menu) {
281         super.onCreateOptionsMenu(menu);
282         menu.add(android.view.Menu.NONE, StkApp.MENU_ID_END_SESSION, 1,
283                 R.string.menu_end_session);
284         menu.add(0, StkApp.MENU_ID_HELP, 2, R.string.help);
285 
286         return true;
287     }
288 
289     @Override
onPrepareOptionsMenu(android.view.Menu menu)290     public boolean onPrepareOptionsMenu(android.view.Menu menu) {
291         super.onPrepareOptionsMenu(menu);
292         menu.findItem(StkApp.MENU_ID_END_SESSION).setVisible(true);
293         menu.findItem(StkApp.MENU_ID_HELP).setVisible(mStkInput.helpAvailable);
294 
295         return true;
296     }
297 
298     @Override
onOptionsItemSelected(MenuItem item)299     public boolean onOptionsItemSelected(MenuItem item) {
300         if (!mAcceptUsersInput) {
301             CatLog.d(LOG_TAG, "mAcceptUsersInput:false");
302             return true;
303         }
304         switch (item.getItemId()) {
305         case StkApp.MENU_ID_END_SESSION:
306             mAcceptUsersInput = false;
307             cancelTimeOut();
308             sendResponse(StkAppService.RES_ID_END_SESSION);
309             finish();
310             return true;
311         case StkApp.MENU_ID_HELP:
312             mAcceptUsersInput = false;
313             cancelTimeOut();
314             sendResponse(StkAppService.RES_ID_INPUT, "", true);
315             finish();
316             return true;
317         }
318         return super.onOptionsItemSelected(item);
319     }
320 
321     @Override
onSaveInstanceState(Bundle outState)322     protected void onSaveInstanceState(Bundle outState) {
323         CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId);
324         outState.putBoolean("ACCEPT_USERS_INPUT", mAcceptUsersInput);
325     }
326 
327     @Override
onRestoreInstanceState(Bundle savedInstanceState)328     protected void onRestoreInstanceState(Bundle savedInstanceState) {
329         CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId);
330         mAcceptUsersInput = savedInstanceState.getBoolean("ACCEPT_USERS_INPUT");
331     }
332 
beforeTextChanged(CharSequence s, int start, int count, int after)333     public void beforeTextChanged(CharSequence s, int start, int count,
334             int after) {
335     }
336 
onTextChanged(CharSequence s, int start, int before, int count)337     public void onTextChanged(CharSequence s, int start, int before, int count) {
338         // Reset timeout.
339         startTimeOut();
340     }
341 
afterTextChanged(Editable s)342     public void afterTextChanged(Editable s) {
343     }
344 
verfiyTypedText()345     private boolean verfiyTypedText() {
346         // If not enough input was typed in stay on the edit screen.
347         if (mTextIn.getText().length() < mStkInput.minLen) {
348             return false;
349         }
350 
351         return true;
352     }
353 
cancelTimeOut()354     private void cancelTimeOut() {
355         mTimeoutHandler.removeMessages(MSG_ID_TIMEOUT);
356     }
357 
startTimeOut()358     private void startTimeOut() {
359         int duration = StkApp.calculateDurationInMilis(mStkInput.duration);
360 
361         if (duration <= 0) {
362             duration = StkApp.UI_TIMEOUT;
363         }
364         cancelTimeOut();
365         mTimeoutHandler.sendMessageDelayed(mTimeoutHandler
366                 .obtainMessage(MSG_ID_TIMEOUT), duration);
367     }
368 
configInputDisplay()369     private void configInputDisplay() {
370         TextView numOfCharsView = (TextView) findViewById(R.id.num_of_chars);
371         TextView inTypeView = (TextView) findViewById(R.id.input_type);
372 
373         int inTypeId = R.string.alphabet;
374 
375         // set the prompt.
376         if (mStkInput.iconSelfExplanatory && mStkInput.icon != null) {
377             mPromptView.setVisibility(View.GONE);
378         } else {
379             mPromptView.setText(mStkInput.text);
380         }
381 
382         // Set input type (alphabet/digit) info close to the InText form.
383         if (mStkInput.digitOnly) {
384             mTextIn.setKeyListener(StkDigitsKeyListener.getInstance());
385             inTypeId = R.string.digits;
386         }
387         inTypeView.setText(inTypeId);
388 
389         TextView textView = (TextView) this.findViewById(R.id.title_text);
390         textView.setText(R.string.app_name);
391 
392         if (mStkInput.icon != null) {
393             ImageView imageView = (ImageView) findViewById(R.id.title_icon);
394             imageView.setImageBitmap(mStkInput.icon);
395             imageView.setVisibility(View.VISIBLE);
396         }
397 
398         // Handle specific global and text attributes.
399         switch (mState) {
400         case STATE_TEXT:
401             int maxLen = mStkInput.maxLen;
402             int minLen = mStkInput.minLen;
403             mTextIn.setFilters(new InputFilter[] {new InputFilter.LengthFilter(
404                     maxLen)});
405 
406             // Set number of chars info.
407             String lengthLimit = String.valueOf(minLen);
408             if (maxLen != minLen) {
409                 lengthLimit = minLen + " - " + maxLen;
410             }
411             numOfCharsView.setText(lengthLimit);
412 
413             if (!mStkInput.echo) {
414                 mTextIn.setInputType(InputType.TYPE_CLASS_NUMBER
415                                      | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
416             }
417             // Set default text if present.
418             if (mStkInput.defaultText != null) {
419                 mTextIn.setText(mStkInput.defaultText);
420             } else {
421                 // make sure the text is cleared
422                 mTextIn.setText("", BufferType.EDITABLE);
423             }
424 
425             break;
426         case STATE_YES_NO:
427             // Set display mode - normal / yes-no layout
428             mYesNoLayout.setVisibility(View.VISIBLE);
429             mNormalLayout.setVisibility(View.GONE);
430             break;
431         }
432     }
433 
getFontSizeFactor(FontSize size)434     private float getFontSizeFactor(FontSize size) {
435         final float[] fontSizes =
436             {NORMAL_FONT_FACTOR, LARGE_FONT_FACTOR, SMALL_FONT_FACTOR};
437 
438         return fontSizes[size.ordinal()];
439     }
440 
initFromIntent(Intent intent)441     private void initFromIntent(Intent intent) {
442         // Get the calling intent type: text/key, and setup the
443         // display parameters.
444         CatLog.d(LOG_TAG, "initFromIntent - slot id: " + mSlotId);
445         if (intent != null) {
446             mStkInput = intent.getParcelableExtra("INPUT");
447             mSlotId = intent.getIntExtra(StkAppService.SLOT_ID, -1);
448             CatLog.d(LOG_TAG, "onCreate - slot id: " + mSlotId);
449             if (mStkInput == null) {
450                 finish();
451             } else {
452                 mState = mStkInput.yesNo ? STATE_YES_NO :
453                         STATE_TEXT;
454                 configInputDisplay();
455             }
456         } else {
457             finish();
458         }
459     }
460 }
461