Files
frameworks_base/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
Jorim Jaggi 5cf17879a3 Reuse KeyguardViewMediator for new Keyguard implementation.
This change reuses KeyguardViewMediator for the new Keyguard
implementation in status bar. KeyguardViewManager is replaced by
StatusBarKeyguardManager which handles adding the view, setting the
state etc. StatusBarWindowManager is introduced to managed the window
of the status bar, which has the logic of both the old Keyguard window
and the old status bar window. In the current implementation, Keyguard
gets displayed like it would be in the bouncer state, but that's likely
to change in the future. Also, setHidden in IKeyguardService is also
renamed to setOccluded, as the word hidden interferes with the
terminology when dismissing the Keyguard.

Bug: 13635952
Change-Id: I1c5d5a49d810d8532089f464cb2efe35e577f517
2014-03-31 20:58:31 +02:00

413 lines
15 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.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import java.io.IOException;
import java.util.List;
public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView {
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 long we stay awake after the user hits the first dot.
private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
// 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 CountDownTimer mCountdownTimer = null;
private LockPatternUtils mLockPatternUtils;
private LockPatternView mLockPatternView;
private Button mForgotPatternButton;
private KeyguardSecurityCallback mCallback;
private boolean mEnableFallback;
/**
* 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() {
public void run() {
mLockPatternView.clearPattern();
}
};
private Rect mTempRect = new Rect();
private SecurityMessageDisplay mSecurityMessageDisplay;
private View mEcaView;
private Drawable mBouncerFrame;
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);
}
public void setKeyguardCallback(KeyguardSecurityCallback callback) {
mCallback = callback;
}
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.setFocusable(false);
mLockPatternView.setOnPatternListener(new UnlockPatternListener());
// stealth mode will be the same for the life of this screen
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
// vibrate mode will be the same for the life of this screen
mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button);
// note: some configurations don't have an emergency call area
if (mForgotPatternButton != null) {
mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
mForgotPatternButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mCallback.showBackupSecurity();
}
});
}
setFocusableInTouchMode(true);
maybeEnableFallback(mContext);
mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
if (bouncerFrameView != null) {
mBouncerFrame = bouncerFrameView.getBackground();
}
}
private void updateFooter(FooterMode mode) {
if (mForgotPatternButton == null) return; // no ECA? no footer
switch (mode) {
case Normal:
if (DEBUG) Log.d(TAG, "mode normal");
mForgotPatternButton.setVisibility(View.GONE);
break;
case ForgotLockPattern:
if (DEBUG) Log.d(TAG, "mode ForgotLockPattern");
mForgotPatternButton.setVisibility(View.VISIBLE);
break;
case VerifyUnlocked:
if (DEBUG) Log.d(TAG, "mode VerifyUnlocked");
mForgotPatternButton.setVisibility(View.GONE);
}
}
@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;
}
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();
if (deadline != 0) {
handleAttemptLockout(deadline);
} else {
displayDefaultSecurityMessage();
}
// the footer depends on how many total attempts the user has failed
if (mCallback.isVerifyUnlockOnly()) {
updateFooter(FooterMode.VerifyUnlocked);
} else if (mEnableFallback &&
(mKeyguardUpdateMonitor.getFailedUnlockAttempts()
>= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
updateFooter(FooterMode.ForgotLockPattern);
} else {
updateFooter(FooterMode.Normal);
}
}
private void displayDefaultSecurityMessage() {
if (mKeyguardUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true);
} else {
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);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
// when timeout dialog closes we want to update our state
reset();
}
}
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
public void onPatternStart() {
mLockPatternView.removeCallbacks(mCancelPatternRunnable);
}
public void onPatternCleared() {
}
public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
// To guard against accidental poking of the wakelock, look for
// the user actually trying to draw a pattern of some minimal length.
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
} else {
// Give just a little extra time if they hit one of the first few dots
mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
}
}
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
if (mLockPatternUtils.checkPattern(pattern)) {
mCallback.reportUnlockAttempt(true);
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
mCallback.dismiss(true);
} else {
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
}
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
boolean registeredAttempt =
pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL;
if (registeredAttempt) {
mCallback.reportUnlockAttempt(false);
}
int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts();
if (registeredAttempt &&
0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
handleAttemptLockout(deadline);
} else {
mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
}
}
}
}
private void maybeEnableFallback(Context context) {
// Ask the account manager if we have an account that can be used as a
// fallback in case the user forgets his pattern.
AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context));
accountAnalyzer.start();
}
private class AccountAnalyzer implements AccountManagerCallback<Bundle> {
private final AccountManager mAccountManager;
private final Account[] mAccounts;
private int mAccountIndex;
private AccountAnalyzer(AccountManager accountManager) {
mAccountManager = accountManager;
mAccounts = accountManager.getAccountsByTypeAsUser("com.google",
new UserHandle(mLockPatternUtils.getCurrentUser()));
}
private void next() {
// if we are ready to enable the fallback or if we depleted the list of accounts
// then finish and get out
if (mEnableFallback || mAccountIndex >= mAccounts.length) {
return;
}
// lookup the confirmCredentials intent for the current account
mAccountManager.confirmCredentialsAsUser(mAccounts[mAccountIndex], null, null, this,
null, new UserHandle(mLockPatternUtils.getCurrentUser()));
}
public void start() {
mEnableFallback = false;
mAccountIndex = 0;
next();
}
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
if (result.getParcelable(AccountManager.KEY_INTENT) != null) {
mEnableFallback = true;
}
} catch (OperationCanceledException e) {
// just skip the account if we are unable to query it
} catch (IOException e) {
// just skip the account if we are unable to query it
} catch (AuthenticatorException e) {
// just skip the account if we are unable to query it
} finally {
mAccountIndex++;
next();
}
}
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
mLockPatternView.clearPattern();
mLockPatternView.setEnabled(false);
final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mEnableFallback) {
updateFooter(FooterMode.ForgotLockPattern);
}
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();
// TODO mUnlockIcon.setVisibility(View.VISIBLE);
if (mEnableFallback) {
updateFooter(FooterMode.ForgotLockPattern);
} else {
updateFooter(FooterMode.Normal);
}
}
}.start();
}
@Override
public boolean needsInput() {
return false;
}
@Override
public void onPause() {
if (mCountdownTimer != null) {
mCountdownTimer.cancel();
mCountdownTimer = null;
}
}
@Override
public void onResume(int reason) {
reset();
}
@Override
public KeyguardSecurityCallback getCallback() {
return mCallback;
}
@Override
public void showBouncer(int duration) {
KeyguardSecurityViewHelper.
showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
}
@Override
public void hideBouncer(int duration) {
KeyguardSecurityViewHelper.
hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
}
}