1/*
2 * Copyright (C) 2010 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.admin.DevicePolicyManager;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.graphics.Rect;
23
24import com.android.internal.policy.impl.PatternUnlockScreen.FooterMode;
25import com.android.internal.widget.LockPatternUtils;
26import com.android.internal.widget.PasswordEntryKeyboardView;
27
28import android.os.CountDownTimer;
29import android.os.SystemClock;
30import android.telephony.TelephonyManager;
31import android.text.method.DigitsKeyListener;
32import android.text.method.TextKeyListener;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.inputmethod.EditorInfo;
37import android.widget.Button;
38import android.widget.EditText;
39import android.widget.LinearLayout;
40import android.widget.TextView;
41import android.widget.TextView.OnEditorActionListener;
42
43import com.android.internal.R;
44import com.android.internal.widget.PasswordEntryKeyboardHelper;
45
46/**
47 * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter
48 * an unlock password
49 */
50public class PasswordUnlockScreen extends LinearLayout implements KeyguardScreen,
51        View.OnClickListener, KeyguardUpdateMonitor.InfoCallback, OnEditorActionListener {
52
53    private final KeyguardUpdateMonitor mUpdateMonitor;
54    private final KeyguardScreenCallback mCallback;
55
56    private EditText mPasswordEntry;
57    private Button mEmergencyCallButton;
58    private LockPatternUtils mLockPatternUtils;
59    private PasswordEntryKeyboardView mKeyboardView;
60    private PasswordEntryKeyboardHelper mKeyboardHelper;
61
62    private int mCreationOrientation;
63    private int mCreationHardKeyboardHidden;
64    private CountDownTimer mCountdownTimer;
65    private TextView mTitle;
66
67    // To avoid accidental lockout due to events while the device in in the pocket, ignore
68    // any passwords with length less than or equal to this length.
69    private static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
70
71    public PasswordUnlockScreen(Context context, Configuration configuration,
72            LockPatternUtils lockPatternUtils, KeyguardUpdateMonitor updateMonitor,
73            KeyguardScreenCallback callback) {
74        super(context);
75
76        mCreationHardKeyboardHidden = configuration.hardKeyboardHidden;
77        mCreationOrientation = configuration.orientation;
78        mUpdateMonitor = updateMonitor;
79        mCallback = callback;
80        mLockPatternUtils = lockPatternUtils;
81
82        LayoutInflater layoutInflater = LayoutInflater.from(context);
83        if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
84            layoutInflater.inflate(R.layout.keyguard_screen_password_portrait, this, true);
85        } else {
86            layoutInflater.inflate(R.layout.keyguard_screen_password_landscape, this, true);
87        }
88
89        final int quality = lockPatternUtils.getKeyguardStoredPasswordQuality();
90        final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
91                || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality;
92
93        mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
94        mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
95        mPasswordEntry.setOnEditorActionListener(this);
96        mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
97        mEmergencyCallButton.setOnClickListener(this);
98        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
99        mTitle = (TextView) findViewById(R.id.enter_password_label);
100
101        mKeyboardHelper = new PasswordEntryKeyboardHelper(context, mKeyboardView, this);
102        mKeyboardHelper.setKeyboardMode(isAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
103                : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
104
105        mKeyboardView.setVisibility(mCreationHardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
106                ? View.INVISIBLE : View.VISIBLE);
107        mPasswordEntry.requestFocus();
108
109        // This allows keyboards with overlapping qwerty/numeric keys to choose just the
110        // numeric keys.
111        if (isAlpha) {
112            mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
113        } else {
114            mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
115        }
116
117        mKeyboardHelper.setVibratePattern(mLockPatternUtils.isTactileFeedbackEnabled() ?
118                com.android.internal.R.array.config_virtualKeyVibePattern : 0);
119    }
120
121    @Override
122    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
123        // send focus to the password field
124        return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
125    }
126
127    /** {@inheritDoc} */
128    public boolean needsInput() {
129        return false;
130    }
131
132    /** {@inheritDoc} */
133    public void onPause() {
134
135    }
136
137    /** {@inheritDoc} */
138    public void onResume() {
139        // start fresh
140        mPasswordEntry.setText("");
141        mPasswordEntry.requestFocus();
142        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
143
144        // if the user is currently locked out, enforce it.
145        long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
146        if (deadline != 0) {
147            handleAttemptLockout(deadline);
148        }
149    }
150
151    /** {@inheritDoc} */
152    public void cleanUp() {
153        mUpdateMonitor.removeCallback(this);
154    }
155
156    public void onClick(View v) {
157        if (v == mEmergencyCallButton) {
158            mCallback.takeEmergencyCallAction();
159        }
160        mCallback.pokeWakelock();
161    }
162
163    private void verifyPasswordAndUnlock() {
164        String entry = mPasswordEntry.getText().toString();
165        if (mLockPatternUtils.checkPassword(entry)) {
166            mCallback.keyguardDone(true);
167            mCallback.reportSuccessfulUnlockAttempt();
168        } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) {
169            // to avoid accidental lockout, only count attempts that are long enough to be a
170            // real password. This may require some tweaking.
171            mCallback.reportFailedUnlockAttempt();
172            if (0 == (mUpdateMonitor.getFailedAttempts()
173                    % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
174                long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
175                handleAttemptLockout(deadline);
176            }
177        }
178        mPasswordEntry.setText("");
179    }
180
181    // Prevent user from using the PIN/Password entry until scheduled deadline.
182    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
183        mPasswordEntry.setEnabled(false);
184        mKeyboardView.setEnabled(false);
185        long elapsedRealtime = SystemClock.elapsedRealtime();
186        mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
187
188            @Override
189            public void onTick(long millisUntilFinished) {
190                int secondsRemaining = (int) (millisUntilFinished / 1000);
191                String instructions = getContext().getString(
192                        R.string.lockscreen_too_many_failed_attempts_countdown,
193                        secondsRemaining);
194                mTitle.setText(instructions);
195            }
196
197            @Override
198            public void onFinish() {
199                mPasswordEntry.setEnabled(true);
200                mTitle.setText(R.string.keyguard_password_enter_password_code);
201                mKeyboardView.setEnabled(true);
202            }
203        }.start();
204    }
205
206
207    @Override
208    public boolean onKeyDown(int keyCode, KeyEvent event) {
209        mCallback.pokeWakelock();
210        return false;
211    }
212
213    @Override
214    protected void onAttachedToWindow() {
215        super.onAttachedToWindow();
216        Configuration config = getResources().getConfiguration();
217        if (config.orientation != mCreationOrientation
218                || config.hardKeyboardHidden != mCreationHardKeyboardHidden) {
219            mCallback.recreateMe(config);
220        }
221    }
222
223    /** {@inheritDoc} */
224    @Override
225    protected void onConfigurationChanged(Configuration newConfig) {
226        super.onConfigurationChanged(newConfig);
227        if (newConfig.orientation != mCreationOrientation
228                || newConfig.hardKeyboardHidden != mCreationHardKeyboardHidden) {
229            mCallback.recreateMe(newConfig);
230        }
231    }
232
233    public void onKeyboardChange(boolean isKeyboardOpen) {
234        // Don't show the soft keyboard when the real keyboard is open
235        mKeyboardView.setVisibility(isKeyboardOpen ? View.INVISIBLE : View.VISIBLE);
236    }
237
238    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
239        // Check if this was the result of hitting the enter key
240        if (actionId == EditorInfo.IME_NULL) {
241            verifyPasswordAndUnlock();
242            return true;
243        }
244        return false;
245    }
246
247    public void onPhoneStateChanged(String newState) {
248        mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
249    }
250
251    public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
252
253    }
254
255    public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
256
257    }
258
259    public void onRingerModeChanged(int state) {
260
261    }
262
263    public void onTimeChanged() {
264
265    }
266
267}
268