[go: nahoru, domu]

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