Files
frameworks_base/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
Selim Cinek cfafe4ea6f Indicating fingerprint error messages on the bouncer now
Also ensure that error messages are surfaced when the screen
comes on such that the user knows why his fingerprint is not
working.

Bug: 22035466
Bug: 22524101
Change-Id: I00b0e833cdb8a3475545ba75b8cb7bf7a419dfd4
2015-08-11 16:37:54 -07:00

428 lines
16 KiB
Java

/*
* 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 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;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AppearAnimationUtils mAppearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtils;
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));
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());
// stealth mode will be the same for the life of this screen
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
KeyguardUpdateMonitor.getCurrentUser()));
// 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.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(R.string.kg_pattern_instructions, false);
}
@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("", false);
}
@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);
}
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
mLockPatternView.enableInput();
onPatternChecked(false, 0, false /* not valid - too short */);
return;
}
mPendingLockCheck = LockPatternChecker.checkPattern(
mLockPatternUtils,
pattern,
KeyguardUpdateMonitor.getCurrentUser(),
new LockPatternChecker.OnCheckCallback() {
@Override
public void onChecked(boolean matched, int timeoutMs) {
mLockPatternView.enableInput();
mPendingLockCheck = null;
onPatternChecked(matched, timeoutMs, true);
}
});
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity();
}
}
private void onPatternChecked(boolean matched, int timeoutMs, boolean isValidPattern) {
if (matched) {
mCallback.reportUnlockAttempt(true, 0);
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
mCallback.dismiss(true);
} else {
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
if (isValidPattern) {
mCallback.reportUnlockAttempt(false, timeoutMs);
if (timeoutMs > 0) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
KeyguardUpdateMonitor.getCurrentUser(), timeoutMs);
handleAttemptLockout(deadline);
}
}
if (timeoutMs == 0) {
mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
}
}
}
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
mLockPatternView.clearPattern();
mLockPatternView.setEnabled(false);
final long elapsedRealtime = SystemClock.elapsedRealtime();
mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
@Override
public void onTick(long millisUntilFinished) {
final int secondsRemaining = (int) (millisUntilFinished / 1000);
mSecurityMessageDisplay.setMessage(
R.string.kg_too_many_failed_attempts_countdown, true, 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,
true /* important */);
break;
default:
}
}
@Override
public void showMessage(String message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
mSecurityMessageDisplay.setMessage(message, true /* important */);
}
@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) {
mLockPatternView.clearPattern();
enableClipping(false);
setTranslationY(0);
AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 300 /* duration */,
-mDisappearAnimationUtils.getStartTranslation(),
mDisappearAnimationUtils.getInterpolator());
mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
new Runnable() {
@Override
public void run() {
enableClipping(true);
if (finishRunnable != null) {
finishRunnable.run();
}
}
}, KeyguardPatternView.this);
if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
200,
- 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;
}
}