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