KeyguardPatternView.java revision 51e3a672e01d6e9ce4b19ec20d9b62ae8e35fc85
1/*
2 * Copyright (C) 2012 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 */
16package com.android.keyguard;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.graphics.Rect;
24import android.os.AsyncTask;
25import android.os.CountDownTimer;
26import android.os.SystemClock;
27import android.text.TextUtils;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.MotionEvent;
31import android.view.RenderNodeAnimator;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.animation.AnimationUtils;
35import android.view.animation.Interpolator;
36import android.widget.LinearLayout;
37
38import com.android.internal.widget.LockPatternChecker;
39import com.android.internal.widget.LockPatternUtils;
40import com.android.internal.widget.LockPatternView;
41import com.android.settingslib.animation.AppearAnimationCreator;
42import com.android.settingslib.animation.AppearAnimationUtils;
43import com.android.settingslib.animation.DisappearAnimationUtils;
44
45import java.util.List;
46
47public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
48        AppearAnimationCreator<LockPatternView.CellState>,
49        EmergencyButton.EmergencyButtonCallback {
50
51    private static final String TAG = "SecurityPatternView";
52    private static final boolean DEBUG = KeyguardConstants.DEBUG;
53
54    // how long before we clear the wrong pattern
55    private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
56
57    // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
58    private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
59
60    // how many cells the user has to cross before we poke the wakelock
61    private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
62
63    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
64    private final AppearAnimationUtils mAppearAnimationUtils;
65    private final DisappearAnimationUtils mDisappearAnimationUtils;
66
67    private CountDownTimer mCountdownTimer = null;
68    private LockPatternUtils mLockPatternUtils;
69    private AsyncTask<?, ?, ?> mPendingLockCheck;
70    private LockPatternView mLockPatternView;
71    private KeyguardSecurityCallback mCallback;
72
73    /**
74     * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
75     * Initialized to something guaranteed to make us poke the wakelock when the user starts
76     * drawing the pattern.
77     * @see #dispatchTouchEvent(android.view.MotionEvent)
78     */
79    private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
80
81    /**
82     * Useful for clearing out the wrong pattern after a delay
83     */
84    private Runnable mCancelPatternRunnable = new Runnable() {
85        @Override
86        public void run() {
87            mLockPatternView.clearPattern();
88        }
89    };
90    private Rect mTempRect = new Rect();
91    private KeyguardMessageArea mSecurityMessageDisplay;
92    private View mEcaView;
93    private ViewGroup mContainer;
94    private int mDisappearYTranslation;
95
96    enum FooterMode {
97        Normal,
98        ForgotLockPattern,
99        VerifyUnlocked
100    }
101
102    public KeyguardPatternView(Context context) {
103        this(context, null);
104    }
105
106    public KeyguardPatternView(Context context, AttributeSet attrs) {
107        super(context, attrs);
108        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
109        mAppearAnimationUtils = new AppearAnimationUtils(context,
110                AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
111                2.0f /* delayScale */, AnimationUtils.loadInterpolator(
112                        mContext, android.R.interpolator.linear_out_slow_in));
113        mDisappearAnimationUtils = new DisappearAnimationUtils(context,
114                125, 1.2f /* translationScale */,
115                0.6f /* delayScale */, AnimationUtils.loadInterpolator(
116                        mContext, android.R.interpolator.fast_out_linear_in));
117        mDisappearYTranslation = getResources().getDimensionPixelSize(
118                R.dimen.disappear_y_translation);
119    }
120
121    @Override
122    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
123        mCallback = callback;
124    }
125
126    @Override
127    public void setLockPatternUtils(LockPatternUtils utils) {
128        mLockPatternUtils = utils;
129    }
130
131    @Override
132    protected void onFinishInflate() {
133        super.onFinishInflate();
134        mLockPatternUtils = mLockPatternUtils == null
135                ? new LockPatternUtils(mContext) : mLockPatternUtils;
136
137        mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
138        mLockPatternView.setSaveEnabled(false);
139        mLockPatternView.setOnPatternListener(new UnlockPatternListener());
140
141        // stealth mode will be the same for the life of this screen
142        mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
143                KeyguardUpdateMonitor.getCurrentUser()));
144
145        // vibrate mode will be the same for the life of this screen
146        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
147
148        mSecurityMessageDisplay =
149                (KeyguardMessageArea) KeyguardMessageArea.findSecurityMessageDisplay(this);
150        mEcaView = findViewById(R.id.keyguard_selector_fade_container);
151        mContainer = (ViewGroup) findViewById(R.id.container);
152
153        EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button);
154        if (button != null) {
155            button.setCallback(this);
156        }
157    }
158
159    @Override
160    public void onEmergencyButtonClickedWhenInCall() {
161        mCallback.reset();
162    }
163
164    @Override
165    public boolean onTouchEvent(MotionEvent ev) {
166        boolean result = super.onTouchEvent(ev);
167        // as long as the user is entering a pattern (i.e sending a touch event that was handled
168        // by this screen), keep poking the wake lock so that the screen will stay on.
169        final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
170        if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
171            mLastPokeTime = SystemClock.elapsedRealtime();
172        }
173        mTempRect.set(0, 0, 0, 0);
174        offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
175        ev.offsetLocation(mTempRect.left, mTempRect.top);
176        result = mLockPatternView.dispatchTouchEvent(ev) || result;
177        ev.offsetLocation(-mTempRect.left, -mTempRect.top);
178        return result;
179    }
180
181    @Override
182    public void reset() {
183        // reset lock pattern
184        mLockPatternView.enableInput();
185        mLockPatternView.setEnabled(true);
186        mLockPatternView.clearPattern();
187
188        // if the user is currently locked out, enforce it.
189        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
190                KeyguardUpdateMonitor.getCurrentUser());
191        if (deadline != 0) {
192            handleAttemptLockout(deadline);
193        } else {
194            displayDefaultSecurityMessage();
195        }
196    }
197
198    private void displayDefaultSecurityMessage() {
199        mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
200    }
201
202    @Override
203    public void showUsabilityHint() {
204    }
205
206    /** TODO: hook this up */
207    public void cleanUp() {
208        if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
209        mLockPatternUtils = null;
210        mLockPatternView.setOnPatternListener(null);
211    }
212
213    private class UnlockPatternListener implements LockPatternView.OnPatternListener {
214
215        @Override
216        public void onPatternStart() {
217            mLockPatternView.removeCallbacks(mCancelPatternRunnable);
218            mSecurityMessageDisplay.setMessage("", false);
219        }
220
221        @Override
222        public void onPatternCleared() {
223        }
224
225        @Override
226        public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
227            mCallback.userActivity();
228        }
229
230        @Override
231        public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
232            mLockPatternView.disableInput();
233            if (mPendingLockCheck != null) {
234                mPendingLockCheck.cancel(false);
235            }
236
237            if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
238                mLockPatternView.enableInput();
239                onPatternChecked(false, 0, false /* not valid - too short */);
240                return;
241            }
242
243            mPendingLockCheck = LockPatternChecker.checkPattern(
244                    mLockPatternUtils,
245                    pattern,
246                    KeyguardUpdateMonitor.getCurrentUser(),
247                    new LockPatternChecker.OnCheckCallback() {
248                        @Override
249                        public void onChecked(boolean matched, int timeoutMs) {
250                            mLockPatternView.enableInput();
251                            mPendingLockCheck = null;
252                            onPatternChecked(matched, timeoutMs, true);
253                        }
254                    });
255            if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
256                mCallback.userActivity();
257            }
258        }
259
260        private void onPatternChecked(boolean matched, int timeoutMs, boolean isValidPattern) {
261            if (matched) {
262                mCallback.reportUnlockAttempt(true, 0);
263                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
264                mCallback.dismiss(true);
265            } else {
266                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
267                if (isValidPattern) {
268                    mCallback.reportUnlockAttempt(false, timeoutMs);
269                    if (timeoutMs > 0) {
270                        long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
271                                KeyguardUpdateMonitor.getCurrentUser(), timeoutMs);
272                        handleAttemptLockout(deadline);
273                    }
274                }
275                if (timeoutMs == 0) {
276                    mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
277                    mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
278                }
279            }
280        }
281    }
282
283    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
284        mLockPatternView.clearPattern();
285        mLockPatternView.setEnabled(false);
286        final long elapsedRealtime = SystemClock.elapsedRealtime();
287
288        mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
289
290            @Override
291            public void onTick(long millisUntilFinished) {
292                final int secondsRemaining = (int) (millisUntilFinished / 1000);
293                mSecurityMessageDisplay.setMessage(
294                        R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
295            }
296
297            @Override
298            public void onFinish() {
299                mLockPatternView.setEnabled(true);
300                displayDefaultSecurityMessage();
301            }
302
303        }.start();
304    }
305
306    @Override
307    public boolean needsInput() {
308        return false;
309    }
310
311    @Override
312    public void onPause() {
313        if (mCountdownTimer != null) {
314            mCountdownTimer.cancel();
315            mCountdownTimer = null;
316        }
317        if (mPendingLockCheck != null) {
318            mPendingLockCheck.cancel(false);
319            mPendingLockCheck = null;
320        }
321    }
322
323    @Override
324    public void onResume(int reason) {
325        reset();
326    }
327
328    @Override
329    public KeyguardSecurityCallback getCallback() {
330        return mCallback;
331    }
332
333    @Override
334    public void showPromptReason(int reason) {
335        switch (reason) {
336            case PROMPT_REASON_RESTART:
337                mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern,
338                        true /* important */);
339                break;
340            default:
341        }
342    }
343
344    @Override
345    public void startAppearAnimation() {
346        enableClipping(false);
347        setAlpha(1f);
348        setTranslationY(mAppearAnimationUtils.getStartTranslation());
349        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
350                0, mAppearAnimationUtils.getInterpolator());
351        mAppearAnimationUtils.startAnimation2d(
352                mLockPatternView.getCellStates(),
353                new Runnable() {
354                    @Override
355                    public void run() {
356                        enableClipping(true);
357                    }
358                },
359                this);
360        if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
361            mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
362                    AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
363                    mAppearAnimationUtils.getStartTranslation(),
364                    true /* appearing */,
365                    mAppearAnimationUtils.getInterpolator(),
366                    null /* finishRunnable */);
367        }
368    }
369
370    @Override
371    public boolean startDisappearAnimation(final Runnable finishRunnable) {
372        mLockPatternView.clearPattern();
373        enableClipping(false);
374        setTranslationY(0);
375        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 300 /* duration */,
376                -mDisappearAnimationUtils.getStartTranslation(),
377                mDisappearAnimationUtils.getInterpolator());
378        mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
379                new Runnable() {
380                    @Override
381                    public void run() {
382                        enableClipping(true);
383                        if (finishRunnable != null) {
384                            finishRunnable.run();
385                        }
386                    }
387                }, KeyguardPatternView.this);
388        if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
389            mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
390                    200,
391                    - mDisappearAnimationUtils.getStartTranslation() * 3,
392                    false /* appearing */,
393                    mDisappearAnimationUtils.getInterpolator(),
394                    null /* finishRunnable */);
395        }
396        return true;
397    }
398
399    private void enableClipping(boolean enable) {
400        setClipChildren(enable);
401        mContainer.setClipToPadding(enable);
402        mContainer.setClipChildren(enable);
403    }
404
405    @Override
406    public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
407            long duration, float translationY, final boolean appearing,
408            Interpolator interpolator,
409            final Runnable finishListener) {
410        mLockPatternView.startCellStateAnimation(animatedCell,
411                1f, appearing ? 1f : 0f, /* alpha */
412                appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
413                appearing ? 0f : 1f, 1f /* scale */,
414                delay, duration, interpolator, finishListener);
415        if (finishListener != null) {
416            // Also animate the Emergency call
417            mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
418                    appearing, interpolator, null);
419        }
420    }
421
422    @Override
423    public boolean hasOverlappingRendering() {
424        return false;
425    }
426}
427