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        KeyguardUpdateMonitor.InfoCallback {
44
45    private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
46
47    private final KeyguardUpdateMonitor mUpdateMonitor;
48    private final KeyguardScreenCallback mCallback;
49
50    private TextView mHeaderText;
51    private TextView mPinText;
52
53    private TextView mOkButton;
54    private Button mEmergencyCallButton;
55
56    private View mBackSpaceButton;
57
58    private final int[] mEnteredPin = {0, 0, 0, 0, 0, 0, 0, 0};
59    private int mEnteredDigits = 0;
60
61    private ProgressDialog mSimUnlockProgressDialog = null;
62
63    private LockPatternUtils mLockPatternUtils;
64
65    private int mCreationOrientation;
66
67    private int mKeyboardHidden;
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        mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
96        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
97        mOkButton = (TextView) findViewById(R.id.ok);
98
99        mHeaderText.setText(R.string.keyguard_password_enter_pin_code);
100        mPinText.setFocusable(false);
101
102        mEmergencyCallButton.setOnClickListener(this);
103        mOkButton.setOnClickListener(this);
104
105        setFocusableInTouchMode(true);
106    }
107
108    /** {@inheritDoc} */
109    public boolean needsInput() {
110        return true;
111    }
112
113    /** {@inheritDoc} */
114    public void onPause() {
115
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        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
129    }
130
131    /** {@inheritDoc} */
132    public void cleanUp() {
133        // hide the dialog.
134        if (mSimUnlockProgressDialog != null) {
135            mSimUnlockProgressDialog.hide();
136        }
137        mUpdateMonitor.removeCallback(this);
138    }
139
140
141    /**
142     * Since the IPC can block, we want to run the request in a separate thread
143     * with a callback.
144     */
145    private abstract class CheckSimPin extends Thread {
146
147        private final String mPin;
148
149        protected CheckSimPin(String pin) {
150            mPin = pin;
151        }
152
153        abstract void onSimLockChangedResponse(boolean success);
154
155        @Override
156        public void run() {
157            try {
158                final boolean result = ITelephony.Stub.asInterface(ServiceManager
159                        .checkService("phone")).supplyPin(mPin);
160                post(new Runnable() {
161                    public void run() {
162                        onSimLockChangedResponse(result);
163                    }
164                });
165            } catch (RemoteException e) {
166                post(new Runnable() {
167                    public void run() {
168                        onSimLockChangedResponse(false);
169                    }
170                });
171            }
172        }
173    }
174
175    public void onClick(View v) {
176        if (v == mBackSpaceButton) {
177            final Editable digits = mPinText.getEditableText();
178            final int len = digits.length();
179            if (len > 0) {
180                digits.delete(len-1, len);
181                mEnteredDigits--;
182            }
183            mCallback.pokeWakelock();
184        } else if (v == mEmergencyCallButton) {
185            mCallback.takeEmergencyCallAction();
186        } else if (v == mOkButton) {
187            checkPin();
188        }
189    }
190
191    private Dialog getSimUnlockProgressDialog() {
192        if (mSimUnlockProgressDialog == null) {
193            mSimUnlockProgressDialog = new ProgressDialog(mContext);
194            mSimUnlockProgressDialog.setMessage(
195                    mContext.getString(R.string.lockscreen_sim_unlock_progress_dialog_message));
196            mSimUnlockProgressDialog.setIndeterminate(true);
197            mSimUnlockProgressDialog.setCancelable(false);
198            mSimUnlockProgressDialog.getWindow().setType(
199                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
200            if (!mContext.getResources().getBoolean(
201                    com.android.internal.R.bool.config_sf_slowBlur)) {
202                mSimUnlockProgressDialog.getWindow().setFlags(
203                        WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
204                        WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
205            }
206        }
207        return mSimUnlockProgressDialog;
208    }
209
210    private void checkPin() {
211
212        // make sure that the pin is at least 4 digits long.
213        if (mEnteredDigits < 4) {
214            // otherwise, display a message to the user, and don't submit.
215            mHeaderText.setText(R.string.invalidPin);
216            mPinText.setText("");
217            mEnteredDigits = 0;
218            mCallback.pokeWakelock();
219            return;
220        }
221        getSimUnlockProgressDialog().show();
222
223        new CheckSimPin(mPinText.getText().toString()) {
224            void onSimLockChangedResponse(boolean success) {
225                if (mSimUnlockProgressDialog != null) {
226                    mSimUnlockProgressDialog.hide();
227                }
228                if (success) {
229                    // before closing the keyguard, report back that
230                    // the sim is unlocked so it knows right away
231                    mUpdateMonitor.reportSimPinUnlocked();
232                    mCallback.goToUnlockScreen();
233                } else {
234                    mHeaderText.setText(R.string.keyguard_password_wrong_pin_code);
235                    mPinText.setText("");
236                    mEnteredDigits = 0;
237                }
238                mCallback.pokeWakelock();
239            }
240        }.start();
241    }
242
243
244    public boolean onKeyDown(int keyCode, KeyEvent event) {
245        if (keyCode == KeyEvent.KEYCODE_BACK) {
246            mCallback.goToLockScreen();
247            return true;
248        }
249
250        final char match = event.getMatch(DIGITS);
251        if (match != 0) {
252            reportDigit(match - '0');
253            return true;
254        }
255        if (keyCode == KeyEvent.KEYCODE_DEL) {
256            if (mEnteredDigits > 0) {
257                mPinText.onKeyDown(keyCode, event);
258                mEnteredDigits--;
259            }
260            return true;
261        }
262
263        if (keyCode == KeyEvent.KEYCODE_ENTER) {
264            checkPin();
265            return true;
266        }
267
268        return false;
269    }
270
271    private void reportDigit(int digit) {
272        if (mEnteredDigits == 0) {
273            mPinText.setText("");
274        }
275        if (mEnteredDigits == 8) {
276            return;
277        }
278        mPinText.append(Integer.toString(digit));
279        mEnteredPin[mEnteredDigits++] = digit;
280    }
281
282    void updateConfiguration() {
283        Configuration newConfig = getResources().getConfiguration();
284        if (newConfig.orientation != mCreationOrientation) {
285            mCallback.recreateMe(newConfig);
286        } else if (newConfig.hardKeyboardHidden != mKeyboardHidden) {
287            mKeyboardHidden = newConfig.hardKeyboardHidden;
288            final boolean isKeyboardOpen = mKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
289            if (mUpdateMonitor.isKeyguardBypassEnabled() && isKeyboardOpen) {
290                mCallback.goToUnlockScreen();
291            }
292        }
293
294    }
295
296    @Override
297    protected void onAttachedToWindow() {
298        super.onAttachedToWindow();
299        updateConfiguration();
300    }
301
302    /** {@inheritDoc} */
303    @Override
304    protected void onConfigurationChanged(Configuration newConfig) {
305        super.onConfigurationChanged(newConfig);
306        updateConfiguration();
307    }
308
309    /**
310     * Helper class to handle input from touch dialer.  Only relevant when
311     * the keyboard is shut.
312     */
313    private class TouchInput implements View.OnClickListener {
314        private TextView mZero;
315        private TextView mOne;
316        private TextView mTwo;
317        private TextView mThree;
318        private TextView mFour;
319        private TextView mFive;
320        private TextView mSix;
321        private TextView mSeven;
322        private TextView mEight;
323        private TextView mNine;
324        private TextView mCancelButton;
325
326        private TouchInput() {
327            mZero = (TextView) findViewById(R.id.zero);
328            mOne = (TextView) findViewById(R.id.one);
329            mTwo = (TextView) findViewById(R.id.two);
330            mThree = (TextView) findViewById(R.id.three);
331            mFour = (TextView) findViewById(R.id.four);
332            mFive = (TextView) findViewById(R.id.five);
333            mSix = (TextView) findViewById(R.id.six);
334            mSeven = (TextView) findViewById(R.id.seven);
335            mEight = (TextView) findViewById(R.id.eight);
336            mNine = (TextView) findViewById(R.id.nine);
337            mCancelButton = (TextView) findViewById(R.id.cancel);
338
339            mZero.setText("0");
340            mOne.setText("1");
341            mTwo.setText("2");
342            mThree.setText("3");
343            mFour.setText("4");
344            mFive.setText("5");
345            mSix.setText("6");
346            mSeven.setText("7");
347            mEight.setText("8");
348            mNine.setText("9");
349
350            mZero.setOnClickListener(this);
351            mOne.setOnClickListener(this);
352            mTwo.setOnClickListener(this);
353            mThree.setOnClickListener(this);
354            mFour.setOnClickListener(this);
355            mFive.setOnClickListener(this);
356            mSix.setOnClickListener(this);
357            mSeven.setOnClickListener(this);
358            mEight.setOnClickListener(this);
359            mNine.setOnClickListener(this);
360            mCancelButton.setOnClickListener(this);
361        }
362
363
364        public void onClick(View v) {
365            if (v == mCancelButton) {
366                mCallback.goToLockScreen();
367                return;
368            }
369
370            final int digit = checkDigit(v);
371            if (digit >= 0) {
372                mCallback.pokeWakelock(DIGIT_PRESS_WAKE_MILLIS);
373                reportDigit(digit);
374            }
375        }
376
377        private int checkDigit(View v) {
378            int digit = -1;
379            if (v == mZero) {
380                digit = 0;
381            } else if (v == mOne) {
382                digit = 1;
383            } else if (v == mTwo) {
384                digit = 2;
385            } else if (v == mThree) {
386                digit = 3;
387            } else if (v == mFour) {
388                digit = 4;
389            } else if (v == mFive) {
390                digit = 5;
391            } else if (v == mSix) {
392                digit = 6;
393            } else if (v == mSeven) {
394                digit = 7;
395            } else if (v == mEight) {
396                digit = 8;
397            } else if (v == mNine) {
398                digit = 9;
399            }
400            return digit;
401        }
402    }
403
404    public void onPhoneStateChanged(String newState) {
405        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
406    }
407
408    public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
409
410    }
411
412    public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
413
414    }
415
416    public void onRingerModeChanged(int state) {
417
418    }
419
420    public void onTimeChanged() {
421
422    }
423}
424