summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
diff options
context:
space:
mode:
authorJason Monk <jmonk@google.com>2017-01-31 14:29:32 -0500
committerJason Monk <jmonk@google.com>2017-02-10 07:45:58 -0800
commit23f85ec14dab49b2c525dc266d2a1f74f7f9d07c (patch)
treef7543adafe98131df4e94785f26f3cb3dab1ba16 /packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
parentefdb4289597ad1594eb906aeafd2ebdf8854bdc7 (diff)
Move Keyguard to SystemUI
Test: make Change-Id: I3abb67e2b022737d2aa0226bb07f3966ad68fff7
Diffstat (limited to 'packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java')
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java492
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;
+ }
+}