1/*
2 * Copyright (C) 2008 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
17package com.android.internal.policy.impl;
18
19import android.app.Dialog;
20import android.app.ProgressDialog;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25
26import com.android.internal.telephony.ITelephony;
27import com.android.internal.widget.LockPatternUtils;
28
29import android.text.Editable;
30import android.view.KeyEvent;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.WindowManager;
34import android.widget.Button;
35import android.widget.LinearLayout;
36import android.widget.TextView;
37import com.android.internal.R;
38
39/**
40 * Displays a dialer like interface to unlock the SIM PIN.
41 */
42public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, View.OnClickListener {
43
44    private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
45
46    private final KeyguardUpdateMonitor mUpdateMonitor;
47    private final KeyguardScreenCallback mCallback;
48
49    private TextView mHeaderText;
50    private TextView mPinText;
51
52    private TextView mOkButton;
53
54    private View mBackSpaceButton;
55
56    private final int[] mEnteredPin = {0, 0, 0, 0, 0, 0, 0, 0};
57    private int mEnteredDigits = 0;
58
59    private ProgressDialog mSimUnlockProgressDialog = null;
60
61    private LockPatternUtils mLockPatternUtils;
62
63    private int mCreationOrientation;
64
65    private int mKeyboardHidden;
66
67    private KeyguardStatusViewManager mKeyguardStatusViewManager;
68
69    private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
70
71    public SimUnlockScreen(Context context, Configuration configuration,
72            KeyguardUpdateMonitor updateMonitor, KeyguardScreenCallback callback,
73            LockPatternUtils lockpatternutils) {
74        super(context);
75        mUpdateMonitor = updateMonitor;
76        mCallback = callback;
77
78        mCreationOrientation = configuration.orientation;
79        mKeyboardHidden = configuration.hardKeyboardHidden;
80        mLockPatternUtils = lockpatternutils;
81
82        LayoutInflater inflater = LayoutInflater.from(context);
83        if (mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
84            inflater.inflate(R.layout.keyguard_screen_sim_pin_landscape, this, true);
85        } else {
86            inflater.inflate(R.layout.keyguard_screen_sim_pin_portrait, this, true);
87            new TouchInput();
88        }
89
90        mHeaderText = (TextView) findViewById(R.id.headerText);
91        mPinText = (TextView) findViewById(R.id.pinDisplay);
92        mBackSpaceButton = findViewById(R.id.backspace);
93        mBackSpaceButton.setOnClickListener(this);
94
95        mOkButton = (TextView) findViewById(R.id.ok);
96
97        mHeaderText.setText(R.string.keyguard_password_enter_pin_code);
98        mPinText.setFocusable(false);
99
100        mOkButton.setOnClickListener(this);
101
102        mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, updateMonitor,
103                lockpatternutils, callback, false);
104
105        setFocusableInTouchMode(true);
106    }
107
108    /** {@inheritDoc} */
109    public boolean needsInput() {
110        return true;
111    }
112
113    /** {@inheritDoc} */
114    public void onPause() {
115        mKeyguardStatusViewManager.onPause();
116    }
117
118    /** {@inheritDoc} */
119    public void onResume() {
120        // start fresh
121        mHeaderText.setText(R.string.keyguard_password_enter_pin_code);
122
123        // make sure that the number of entered digits is consistent when we
124        // erase the SIM unlock code, including orientation changes.
125        mPinText.setText("");
126        mEnteredDigits = 0;
127
128        mKeyguardStatusViewManager.onResume();
129    }
130
131    /** {@inheritDoc} */
132    public void cleanUp() {
133        // dismiss the dialog.
134        if (mSimUnlockProgressDialog != null) {
135            mSimUnlockProgressDialog.dismiss();
136            mSimUnlockProgressDialog = null;
137        }
138        mUpdateMonitor.removeCallback(this);
139    }
140
141
142    /**
143     * Since the IPC can block, we want to run the request in a separate thread
144     * with a callback.
145     */
146    private abstract class CheckSimPin extends Thread {
147
148        private final String mPin;
149
150        protected CheckSimPin(String pin) {
151            mPin = pin;
152        }
153
154        abstract void onSimLockChangedResponse(boolean success);
155
156        @Override
157        public void run() {
158            try {
159                final boolean result = ITelephony.Stub.asInterface(ServiceManager
160                        .checkService("phone")).supplyPin(mPin);
161                post(new Runnable() {
162                    public void run() {
163                        onSimLockChangedResponse(result);
164                    }
165                });
166            } catch (RemoteException e) {
167                post(new Runnable() {
168                    public void run() {
169                        onSimLockChangedResponse(false);
170                    }
171                });
172            }
173        }
174    }
175
176    public void onClick(View v) {
177        if (v == mBackSpaceButton) {
178            final Editable digits = mPinText.getEditableText();
179            final int len = digits.length();
180            if (len > 0) {
181                digits.delete(len-1, len);
182                mEnteredDigits--;
183            }
184            mCallback.pokeWakelock();
185        } else if (v == mOkButton) {
186            checkPin();
187        }
188    }
189
190    private Dialog getSimUnlockProgressDialog() {
191        if (mSimUnlockProgressDialog == null) {
192            mSimUnlockProgressDialog = new ProgressDialog(mContext);
193            mSimUnlockProgressDialog.setMessage(
194                    mContext.getString(R.string.lockscreen_sim_unlock_progress_dialog_message));
195            mSimUnlockProgressDialog.setIndeterminate(true);
196            mSimUnlockProgressDialog.setCancelable(false);
197            mSimUnlockProgressDialog.getWindow().setType(
198                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
199        }
200        return mSimUnlockProgressDialog;
201    }
202
203    private void checkPin() {
204
205        // make sure that the pin is at least 4 digits long.
206        if (mEnteredDigits < 4) {
207            // otherwise, display a message to the user, and don't submit.
208            mHeaderText.setText(R.string.invalidPin);
209            mPinText.setText("");
210            mEnteredDigits = 0;
211            mCallback.pokeWakelock();
212            return;
213        }
214        getSimUnlockProgressDialog().show();
215
216        new CheckSimPin(mPinText.getText().toString()) {
217            void onSimLockChangedResponse(final boolean success) {
218                mPinText.post(new Runnable() {
219                    public void run() {
220                        if (mSimUnlockProgressDialog != null) {
221                            mSimUnlockProgressDialog.hide();
222                        }
223                        if (success) {
224                            // before closing the keyguard, report back that
225                            // the sim is unlocked so it knows right away
226                            mUpdateMonitor.reportSimUnlocked();
227                            mCallback.goToUnlockScreen();
228                        } else {
229                            mHeaderText.setText(R.string.keyguard_password_wrong_pin_code);
230                            mPinText.setText("");
231                            mEnteredDigits = 0;
232                        }
233                        mCallback.pokeWakelock();
234                    }
235                });
236            }
237        }.start();
238    }
239
240
241    public boolean onKeyDown(int keyCode, KeyEvent event) {
242        if (keyCode == KeyEvent.KEYCODE_BACK) {
243            mCallback.goToLockScreen();
244            return true;
245        }
246
247        final char match = event.getMatch(DIGITS);
248        if (match != 0) {
249            reportDigit(match - '0');
250            return true;
251        }
252        if (keyCode == KeyEvent.KEYCODE_DEL) {
253            if (mEnteredDigits > 0) {
254                mPinText.onKeyDown(keyCode, event);
255                mEnteredDigits--;
256            }
257            return true;
258        }
259
260        if (keyCode == KeyEvent.KEYCODE_ENTER) {
261            checkPin();
262            return true;
263        }
264
265        return false;
266    }
267
268    private void reportDigit(int digit) {
269        if (mEnteredDigits == 0) {
270            mPinText.setText("");
271        }
272        if (mEnteredDigits == 8) {
273            return;
274        }
275        mPinText.append(Integer.toString(digit));
276        mEnteredPin[mEnteredDigits++] = digit;
277    }
278
279    void updateConfiguration() {
280        Configuration newConfig = getResources().getConfiguration();
281        if (newConfig.orientation != mCreationOrientation) {
282            mCallback.recreateMe(newConfig);
283        } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) {
284            mKeyboardHidden = newConfig.hardKeyboardHidden;
285        }
286    }
287
288    @Override
289    protected void onAttachedToWindow() {
290        super.onAttachedToWindow();
291        updateConfiguration();
292    }
293
294    /** {@inheritDoc} */
295    @Override
296    protected void onConfigurationChanged(Configuration newConfig) {
297        super.onConfigurationChanged(newConfig);
298        updateConfiguration();
299    }
300
301    /**
302     * Helper class to handle input from touch dialer.  Only relevant when
303     * the keyboard is shut.
304     */
305    private class TouchInput implements View.OnClickListener {
306        private TextView mZero;
307        private TextView mOne;
308        private TextView mTwo;
309        private TextView mThree;
310        private TextView mFour;
311        private TextView mFive;
312        private TextView mSix;
313        private TextView mSeven;
314        private TextView mEight;
315        private TextView mNine;
316        private TextView mCancelButton;
317
318        private TouchInput() {
319            mZero = (TextView) findViewById(R.id.zero);
320            mOne = (TextView) findViewById(R.id.one);
321            mTwo = (TextView) findViewById(R.id.two);
322            mThree = (TextView) findViewById(R.id.three);
323            mFour = (TextView) findViewById(R.id.four);
324            mFive = (TextView) findViewById(R.id.five);
325            mSix = (TextView) findViewById(R.id.six);
326            mSeven = (TextView) findViewById(R.id.seven);
327            mEight = (TextView) findViewById(R.id.eight);
328            mNine = (TextView) findViewById(R.id.nine);
329            mCancelButton = (TextView) findViewById(R.id.cancel);
330
331            mZero.setText("0");
332            mOne.setText("1");
333            mTwo.setText("2");
334            mThree.setText("3");
335            mFour.setText("4");
336            mFive.setText("5");
337            mSix.setText("6");
338            mSeven.setText("7");
339            mEight.setText("8");
340            mNine.setText("9");
341
342            mZero.setOnClickListener(this);
343            mOne.setOnClickListener(this);
344            mTwo.setOnClickListener(this);
345            mThree.setOnClickListener(this);
346            mFour.setOnClickListener(this);
347            mFive.setOnClickListener(this);
348            mSix.setOnClickListener(this);
349            mSeven.setOnClickListener(this);
350            mEight.setOnClickListener(this);
351            mNine.setOnClickListener(this);
352            mCancelButton.setOnClickListener(this);
353        }
354
355
356        public void onClick(View v) {
357            if (v == mCancelButton) {
358                mPinText.setText(""); // clear the PIN entry field if the user cancels
359                mCallback.goToLockScreen();
360                return;
361            }
362
363            final int digit = checkDigit(v);
364            if (digit >= 0) {
365                mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
366                reportDigit(digit);
367            }
368        }
369
370        private int checkDigit(View v) {
371            int digit = -1;
372            if (v == mZero) {
373                digit = 0;
374            } else if (v == mOne) {
375                digit = 1;
376            } else if (v == mTwo) {
377                digit = 2;
378            } else if (v == mThree) {
379                digit = 3;
380            } else if (v == mFour) {
381                digit = 4;
382            } else if (v == mFive) {
383                digit = 5;
384            } else if (v == mSix) {
385                digit = 6;
386            } else if (v == mSeven) {
387                digit = 7;
388            } else if (v == mEight) {
389                digit = 8;
390            } else if (v == mNine) {
391                digit = 9;
392            }
393            return digit;
394        }
395    }
396}
397