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
17package com.android.stk;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.drawable.BitmapDrawable;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.text.Editable;
28import android.text.InputFilter;
29import android.text.InputType;
30import android.text.TextWatcher;
31import android.text.method.PasswordTransformationMethod;
32import android.view.KeyEvent;
33import android.view.MenuItem;
34import android.view.View;
35import android.view.Window;
36import android.widget.Button;
37import android.widget.ImageView;
38import android.widget.TextView;
39import android.widget.EditText;
40import android.widget.TextView.BufferType;
41import com.android.internal.telephony.cat.CatLog;
42import com.android.internal.telephony.cat.FontSize;
43import com.android.internal.telephony.cat.Input;
44
45/**
46 * Display a request for a text input a long with a text edit form.
47 */
48public 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..
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
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
175    protected void onPostCreate(Bundle savedInstanceState) {
176        super.onPostCreate(savedInstanceState);
177
178        mTextIn.addTextChangedListener(this);
179    }
180
181    @Override
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
195    public void onPause() {
196        super.onPause();
197        CatLog.d(LOG_TAG, "onPause - mIsResponseSent[" + mIsResponseSent + "]");
198    }
199
200    @Override
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
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
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
249    void sendResponse(int resId) {
250        sendResponse(resId, null, false);
251    }
252
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
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
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
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
322    protected void onSaveInstanceState(Bundle outState) {
323        CatLog.d(LOG_TAG, "onSaveInstanceState: " + mSlotId);
324        outState.putBoolean("ACCEPT_USERS_INPUT", mAcceptUsersInput);
325    }
326
327    @Override
328    protected void onRestoreInstanceState(Bundle savedInstanceState) {
329        CatLog.d(LOG_TAG, "onRestoreInstanceState: " + mSlotId);
330        mAcceptUsersInput = savedInstanceState.getBoolean("ACCEPT_USERS_INPUT");
331    }
332
333    public void beforeTextChanged(CharSequence s, int start, int count,
334            int after) {
335    }
336
337    public void onTextChanged(CharSequence s, int start, int before, int count) {
338        // Reset timeout.
339        startTimeOut();
340    }
341
342    public void afterTextChanged(Editable s) {
343    }
344
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
354    private void cancelTimeOut() {
355        mTimeoutHandler.removeMessages(MSG_ID_TIMEOUT);
356    }
357
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
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
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
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