diff options
Diffstat (limited to 'packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java')
-rw-r--r-- | packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java new file mode 100644 index 000000000000..c2b57ffa6113 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard; + +import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; + +import android.content.Context; +import android.graphics.Rect; +import android.os.AsyncTask; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.LinearLayout; + +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.settingslib.animation.AppearAnimationCreator; +import com.android.settingslib.animation.AppearAnimationUtils; +import com.android.settingslib.animation.DisappearAnimationUtils; + +import java.util.List; + +public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView, + AppearAnimationCreator<LockPatternView.CellState>, + EmergencyButton.EmergencyButtonCallback { + + private static final String TAG = "SecurityPatternView"; + private static final boolean DEBUG = KeyguardConstants.DEBUG; + + // how long before we clear the wrong pattern + private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000; + + // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK + private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000; + + // how many cells the user has to cross before we poke the wakelock + private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; + + // How much we scale up the duration of the disappear animation when the current user is locked + public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f; + + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final AppearAnimationUtils mAppearAnimationUtils; + private final DisappearAnimationUtils mDisappearAnimationUtils; + private final DisappearAnimationUtils mDisappearAnimationUtilsLocked; + + private CountDownTimer mCountdownTimer = null; + private LockPatternUtils mLockPatternUtils; + private AsyncTask<?, ?, ?> mPendingLockCheck; + private LockPatternView mLockPatternView; + private KeyguardSecurityCallback mCallback; + + /** + * Keeps track of the last time we poked the wake lock during dispatching of the touch event. + * Initialized to something guaranteed to make us poke the wakelock when the user starts + * drawing the pattern. + * @see #dispatchTouchEvent(android.view.MotionEvent) + */ + private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS; + + /** + * Useful for clearing out the wrong pattern after a delay + */ + private Runnable mCancelPatternRunnable = new Runnable() { + @Override + public void run() { + mLockPatternView.clearPattern(); + } + }; + private Rect mTempRect = new Rect(); + private KeyguardMessageArea mSecurityMessageDisplay; + private View mEcaView; + private ViewGroup mContainer; + private int mDisappearYTranslation; + + enum FooterMode { + Normal, + ForgotLockPattern, + VerifyUnlocked + } + + public KeyguardPatternView(Context context) { + this(context, null); + } + + public KeyguardPatternView(Context context, AttributeSet attrs) { + super(context, attrs); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + mAppearAnimationUtils = new AppearAnimationUtils(context, + AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */, + 2.0f /* delayScale */, AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.linear_out_slow_in)); + mDisappearAnimationUtils = new DisappearAnimationUtils(context, + 125, 1.2f /* translationScale */, + 0.6f /* delayScale */, AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.fast_out_linear_in)); + mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context, + (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */, + 0.6f /* delayScale */, AnimationUtils.loadInterpolator( + mContext, android.R.interpolator.fast_out_linear_in)); + mDisappearYTranslation = getResources().getDimensionPixelSize( + R.dimen.disappear_y_translation); + } + + @Override + public void setKeyguardCallback(KeyguardSecurityCallback callback) { + mCallback = callback; + } + + @Override + public void setLockPatternUtils(LockPatternUtils utils) { + mLockPatternUtils = utils; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLockPatternUtils = mLockPatternUtils == null + ? new LockPatternUtils(mContext) : mLockPatternUtils; + + mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView); + mLockPatternView.setSaveEnabled(false); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + + // vibrate mode will be the same for the life of this screen + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + + mSecurityMessageDisplay = + (KeyguardMessageArea) KeyguardMessageArea.findSecurityMessageDisplay(this); + mEcaView = findViewById(R.id.keyguard_selector_fade_container); + mContainer = (ViewGroup) findViewById(R.id.container); + + EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(this); + } + } + + @Override + public void onEmergencyButtonClickedWhenInCall() { + mCallback.reset(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + boolean result = super.onTouchEvent(ev); + // as long as the user is entering a pattern (i.e sending a touch event that was handled + // by this screen), keep poking the wake lock so that the screen will stay on. + final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime; + if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) { + mLastPokeTime = SystemClock.elapsedRealtime(); + } + mTempRect.set(0, 0, 0, 0); + offsetRectIntoDescendantCoords(mLockPatternView, mTempRect); + ev.offsetLocation(mTempRect.left, mTempRect.top); + result = mLockPatternView.dispatchTouchEvent(ev) || result; + ev.offsetLocation(-mTempRect.left, -mTempRect.top); + return result; + } + + @Override + public void reset() { + // reset lock pattern + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); + mLockPatternView.enableInput(); + mLockPatternView.setEnabled(true); + mLockPatternView.clearPattern(); + + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser()); + if (deadline != 0) { + handleAttemptLockout(deadline); + } else { + displayDefaultSecurityMessage(); + } + } + + private void displayDefaultSecurityMessage() { + mSecurityMessageDisplay.setMessage(""); + } + + @Override + public void showUsabilityHint() { + } + + /** TODO: hook this up */ + public void cleanUp() { + if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); + mLockPatternUtils = null; + mLockPatternView.setOnPatternListener(null); + } + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + @Override + public void onPatternStart() { + mLockPatternView.removeCallbacks(mCancelPatternRunnable); + mSecurityMessageDisplay.setMessage(""); + } + + @Override + public void onPatternCleared() { + } + + @Override + public void onPatternCellAdded(List<LockPatternView.Cell> pattern) { + mCallback.userActivity(); + } + + @Override + public void onPatternDetected(final List<LockPatternView.Cell> pattern) { + mLockPatternView.disableInput(); + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + final int userId = KeyguardUpdateMonitor.getCurrentUser(); + if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + mLockPatternView.enableInput(); + onPatternChecked(userId, false, 0, false /* not valid - too short */); + return; + } + + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); + LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + mPendingLockCheck = LockPatternChecker.checkPattern( + mLockPatternUtils, + pattern, + userId, + new LockPatternChecker.OnCheckCallback() { + + @Override + public void onEarlyMatched() { + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL); + } + onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */, + true /* isValidPattern */); + } + + @Override + public void onChecked(boolean matched, int timeoutMs) { + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + mLockPatternView.enableInput(); + mPendingLockCheck = null; + if (!matched) { + onPatternChecked(userId, false /* matched */, timeoutMs, + true /* isValidPattern */); + } + } + + @Override + public void onCancelled() { + // We already got dismissed with the early matched callback, so we + // cancelled the check. However, we still need to note down the latency. + if (LatencyTracker.isEnabled(mContext)) { + LatencyTracker.getInstance(mContext).onActionEnd( + ACTION_CHECK_CREDENTIAL_UNLOCKED); + } + } + }); + if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { + mCallback.userActivity(); + } + } + + private void onPatternChecked(int userId, boolean matched, int timeoutMs, + boolean isValidPattern) { + boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + if (matched) { + mCallback.reportUnlockAttempt(userId, true, 0); + if (dismissKeyguard) { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); + mCallback.dismiss(true, userId); + } + } else { + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); + if (isValidPattern) { + mCallback.reportUnlockAttempt(userId, false, timeoutMs); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + userId, timeoutMs); + handleAttemptLockout(deadline); + } + } + if (timeoutMs == 0) { + mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern); + mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); + } + } + } + } + + private void handleAttemptLockout(long elapsedRealtimeDeadline) { + mLockPatternView.clearPattern(); + mLockPatternView.setEnabled(false); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); + mSecurityMessageDisplay.formatMessage( + R.string.kg_too_many_failed_attempts_countdown, secondsRemaining); + } + + @Override + public void onFinish() { + mLockPatternView.setEnabled(true); + displayDefaultSecurityMessage(); + } + + }.start(); + } + + @Override + public boolean needsInput() { + return false; + } + + @Override + public void onPause() { + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + mPendingLockCheck = null; + } + } + + @Override + public void onResume(int reason) { + reset(); + } + + @Override + public KeyguardSecurityCallback getCallback() { + return mCallback; + } + + @Override + public void showPromptReason(int reason) { + switch (reason) { + case PROMPT_REASON_RESTART: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern); + break; + case PROMPT_REASON_TIMEOUT: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + case PROMPT_REASON_DEVICE_ADMIN: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin); + break; + case PROMPT_REASON_USER_REQUEST: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request); + break; + case PROMPT_REASON_NONE: + break; + default: + mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern); + break; + } + } + + @Override + public void showMessage(String message, int color) { + mSecurityMessageDisplay.setNextMessageColor(color); + mSecurityMessageDisplay.setMessage(message); + } + + @Override + public void startAppearAnimation() { + enableClipping(false); + setAlpha(1f); + setTranslationY(mAppearAnimationUtils.getStartTranslation()); + AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */, + 0, mAppearAnimationUtils.getInterpolator()); + mAppearAnimationUtils.startAnimation2d( + mLockPatternView.getCellStates(), + new Runnable() { + @Override + public void run() { + enableClipping(true); + } + }, + this); + if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { + mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, + AppearAnimationUtils.DEFAULT_APPEAR_DURATION, + mAppearAnimationUtils.getStartTranslation(), + true /* appearing */, + mAppearAnimationUtils.getInterpolator(), + null /* finishRunnable */); + } + } + + @Override + public boolean startDisappearAnimation(final Runnable finishRunnable) { + float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition() + ? DISAPPEAR_MULTIPLIER_LOCKED + : 1f; + mLockPatternView.clearPattern(); + enableClipping(false); + setTranslationY(0); + AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, + (long) (300 * durationMultiplier), + -mDisappearAnimationUtils.getStartTranslation(), + mDisappearAnimationUtils.getInterpolator()); + + DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor + .needsSlowUnlockTransition() + ? mDisappearAnimationUtilsLocked + : mDisappearAnimationUtils; + disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), + () -> { + enableClipping(true); + if (finishRunnable != null) { + finishRunnable.run(); + } + }, KeyguardPatternView.this); + if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { + mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, + (long) (200 * durationMultiplier), + - mDisappearAnimationUtils.getStartTranslation() * 3, + false /* appearing */, + mDisappearAnimationUtils.getInterpolator(), + null /* finishRunnable */); + } + return true; + } + + private void enableClipping(boolean enable) { + setClipChildren(enable); + mContainer.setClipToPadding(enable); + mContainer.setClipChildren(enable); + } + + @Override + public void createAnimation(final LockPatternView.CellState animatedCell, long delay, + long duration, float translationY, final boolean appearing, + Interpolator interpolator, + final Runnable finishListener) { + mLockPatternView.startCellStateAnimation(animatedCell, + 1f, appearing ? 1f : 0f, /* alpha */ + appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */ + appearing ? 0f : 1f, 1f /* scale */, + delay, duration, interpolator, finishListener); + if (finishListener != null) { + // Also animate the Emergency call + mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY, + appearing, interpolator, null); + } + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} |