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