518 lines
19 KiB
Java
518 lines
19 KiB
Java
/*
|
|
* Copyright (C) 2008 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.internal.policy.impl;
|
|
|
|
import android.content.Context;
|
|
import android.os.CountDownTimer;
|
|
import android.os.SystemClock;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.MotionEvent;
|
|
import android.widget.Button;
|
|
import android.widget.TextView;
|
|
import android.text.format.DateFormat;
|
|
import android.text.TextUtils;
|
|
import com.android.internal.R;
|
|
import com.android.internal.telephony.IccCard;
|
|
import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
|
|
import com.android.internal.widget.LockPatternUtils;
|
|
import com.android.internal.widget.LockPatternView;
|
|
import com.android.internal.widget.LockPatternView.Cell;
|
|
|
|
import java.util.List;
|
|
import java.util.Date;
|
|
|
|
/**
|
|
* This is the screen that shows the 9 circle unlock widget and instructs
|
|
* the user how to unlock their device, or make an emergency call.
|
|
*/
|
|
class UnlockScreen extends LinearLayoutWithDefaultTouchRecepient
|
|
implements KeyguardScreen, KeyguardUpdateMonitor.ConfigurationChangeCallback,
|
|
KeyguardUpdateMonitor.InfoCallback, KeyguardUpdateMonitor.SimStateCallback {
|
|
|
|
private static final String TAG = "UnlockScreen";
|
|
|
|
// how long before we clear the wrong pattern
|
|
private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
|
|
|
|
// how long we stay awake once the user is ready to enter a pattern
|
|
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 int mFailedPatternAttemptsSinceLastTimeout = 0;
|
|
private int mTotalFailedPatternAttempts = 0;
|
|
private CountDownTimer mCountdownTimer = null;
|
|
|
|
private final LockPatternUtils mLockPatternUtils;
|
|
private final KeyguardUpdateMonitor mUpdateMonitor;
|
|
private final KeyguardScreenCallback mCallback;
|
|
|
|
/**
|
|
* whether there is a fallback option available when the pattern is forgotten.
|
|
*/
|
|
private boolean mEnableFallback;
|
|
|
|
private boolean mCreatedInPortrait;
|
|
|
|
private TextView mCarrier;
|
|
private TextView mDate;
|
|
|
|
// are we showing battery information?
|
|
private boolean mShowingBatteryInfo = false;
|
|
|
|
// last known plugged in state
|
|
private boolean mPluggedIn = false;
|
|
|
|
// last known battery level
|
|
private int mBatteryLevel = 100;
|
|
|
|
private String mNextAlarm = null;
|
|
|
|
private String mInstructions = null;
|
|
private TextView mStatus1;
|
|
private TextView mStatusSep;
|
|
private TextView mStatus2;
|
|
|
|
|
|
private LockPatternView mLockPatternView;
|
|
|
|
private ViewGroup mFooterNormal;
|
|
private ViewGroup mFooterForgotPattern;
|
|
|
|
/**
|
|
* Keeps track of the last time we poked the wake lock during dispatching
|
|
* of the touch event, initalized to something gauranteed to make us
|
|
* poke it 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 Button mForgotPatternButton;
|
|
|
|
enum FooterMode {
|
|
Normal,
|
|
ForgotLockPattern,
|
|
VerifyUnlocked
|
|
}
|
|
|
|
private void updateFooter(FooterMode mode) {
|
|
switch (mode) {
|
|
case Normal:
|
|
mFooterNormal.setVisibility(View.VISIBLE);
|
|
mFooterForgotPattern.setVisibility(View.GONE);
|
|
break;
|
|
case ForgotLockPattern:
|
|
mFooterNormal.setVisibility(View.GONE);
|
|
mFooterForgotPattern.setVisibility(View.VISIBLE);
|
|
mForgotPatternButton.setVisibility(View.VISIBLE);
|
|
break;
|
|
case VerifyUnlocked:
|
|
mFooterNormal.setVisibility(View.GONE);
|
|
mFooterForgotPattern.setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param context The context.
|
|
* @param lockPatternUtils Used to lookup lock pattern settings.
|
|
* @param updateMonitor Used to lookup state affecting keyguard.
|
|
* @param callback Used to notify the manager when we're done, etc.
|
|
* @param totalFailedAttempts The current number of failed attempts.
|
|
* @param enableFallback True if a backup unlock option is available when the user has forgotten
|
|
* their pattern (e.g they have a google account so we can show them the account based
|
|
* backup option).
|
|
*/
|
|
UnlockScreen(Context context,
|
|
LockPatternUtils lockPatternUtils,
|
|
KeyguardUpdateMonitor updateMonitor,
|
|
KeyguardScreenCallback callback,
|
|
int totalFailedAttempts) {
|
|
super(context);
|
|
mLockPatternUtils = lockPatternUtils;
|
|
mUpdateMonitor = updateMonitor;
|
|
mCallback = callback;
|
|
mTotalFailedPatternAttempts = totalFailedAttempts;
|
|
mFailedPatternAttemptsSinceLastTimeout = totalFailedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
|
|
|
|
if (mUpdateMonitor.isInPortrait()) {
|
|
LayoutInflater.from(context).inflate(R.layout.keyguard_screen_unlock_portrait, this, true);
|
|
} else {
|
|
LayoutInflater.from(context).inflate(R.layout.keyguard_screen_unlock_landscape, this, true);
|
|
}
|
|
|
|
mCarrier = (TextView) findViewById(R.id.carrier);
|
|
mDate = (TextView) findViewById(R.id.date);
|
|
|
|
refreshTimeAndDateDisplay();
|
|
|
|
mStatus1 = (TextView) findViewById(R.id.status1);
|
|
mStatusSep = (TextView) findViewById(R.id.statusSep);
|
|
mStatus2 = (TextView) findViewById(R.id.status2);
|
|
|
|
resetStatusInfo();
|
|
|
|
|
|
mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
|
|
|
|
mFooterNormal = (ViewGroup) findViewById(R.id.footerNormal);
|
|
mFooterForgotPattern = (ViewGroup) findViewById(R.id.footerForgotPattern);
|
|
|
|
// emergency call buttons
|
|
final OnClickListener emergencyClick = new OnClickListener() {
|
|
public void onClick(View v) {
|
|
mCallback.takeEmergencyCallAction();
|
|
}
|
|
};
|
|
Button emergencyAlone = (Button) findViewById(R.id.emergencyCallAlone);
|
|
emergencyAlone.setFocusable(false); // touch only!
|
|
emergencyAlone.setOnClickListener(emergencyClick);
|
|
Button emergencyTogether = (Button) findViewById(R.id.emergencyCallTogether);
|
|
emergencyTogether.setFocusable(false);
|
|
emergencyTogether.setOnClickListener(emergencyClick);
|
|
|
|
mForgotPatternButton = (Button) findViewById(R.id.forgotPattern);
|
|
mForgotPatternButton.setText(R.string.lockscreen_forgot_pattern_button_text);
|
|
mForgotPatternButton.setOnClickListener(new OnClickListener() {
|
|
|
|
public void onClick(View v) {
|
|
mCallback.forgotPattern(true);
|
|
}
|
|
});
|
|
|
|
// make it so unhandled touch events within the unlock screen go to the
|
|
// lock pattern view.
|
|
setDefaultTouchRecepient(mLockPatternView);
|
|
|
|
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());
|
|
|
|
// assume normal footer mode for now
|
|
updateFooter(FooterMode.Normal);
|
|
|
|
mCreatedInPortrait = updateMonitor.isInPortrait();
|
|
updateMonitor.registerInfoCallback(this);
|
|
updateMonitor.registerSimStateCallback(this);
|
|
updateMonitor.registerConfigurationChangeCallback(this);
|
|
setFocusableInTouchMode(true);
|
|
|
|
// until we get an update...
|
|
mCarrier.setText(
|
|
LockScreen.getCarrierString(
|
|
mUpdateMonitor.getTelephonyPlmn(),
|
|
mUpdateMonitor.getTelephonySpn()));
|
|
}
|
|
|
|
public void setEnableFallback(boolean state) {
|
|
mEnableFallback = state;
|
|
}
|
|
|
|
private void resetStatusInfo() {
|
|
mInstructions = null;
|
|
mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo();
|
|
mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
|
|
mBatteryLevel = mUpdateMonitor.getBatteryLevel();
|
|
mNextAlarm = mLockPatternUtils.getNextAlarm();
|
|
updateStatusLines();
|
|
}
|
|
|
|
private void updateStatusLines() {
|
|
if (mInstructions != null) {
|
|
// instructions only
|
|
mStatus1.setText(mInstructions);
|
|
if (TextUtils.isEmpty(mInstructions)) {
|
|
mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
|
} else {
|
|
mStatus1.setCompoundDrawablesWithIntrinsicBounds(
|
|
R.drawable.ic_lock_idle_lock, 0, 0, 0);
|
|
}
|
|
|
|
mStatus1.setVisibility(View.VISIBLE);
|
|
mStatusSep.setVisibility(View.GONE);
|
|
mStatus2.setVisibility(View.GONE);
|
|
} else if (mShowingBatteryInfo && mNextAlarm == null) {
|
|
// battery only
|
|
if (mPluggedIn) {
|
|
if (mBatteryLevel >= 100) {
|
|
mStatus1.setText(getContext().getString(R.string.lockscreen_charged));
|
|
} else {
|
|
mStatus1.setText(getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel));
|
|
}
|
|
} else {
|
|
mStatus1.setText(getContext().getString(R.string.lockscreen_low_battery));
|
|
}
|
|
mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
|
|
|
|
mStatus1.setVisibility(View.VISIBLE);
|
|
mStatusSep.setVisibility(View.GONE);
|
|
mStatus2.setVisibility(View.GONE);
|
|
|
|
} else if (mNextAlarm != null && !mShowingBatteryInfo) {
|
|
// alarm only
|
|
mStatus1.setText(mNextAlarm);
|
|
mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
|
|
|
|
mStatus1.setVisibility(View.VISIBLE);
|
|
mStatusSep.setVisibility(View.GONE);
|
|
mStatus2.setVisibility(View.GONE);
|
|
} else if (mNextAlarm != null && mShowingBatteryInfo) {
|
|
// both battery and next alarm
|
|
mStatus1.setText(mNextAlarm);
|
|
mStatusSep.setText("|");
|
|
mStatus2.setText(getContext().getString(
|
|
R.string.lockscreen_battery_short,
|
|
Math.min(100, mBatteryLevel)));
|
|
mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
|
|
if (mPluggedIn) {
|
|
mStatus2.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
|
|
} else {
|
|
mStatus2.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
|
}
|
|
|
|
mStatus1.setVisibility(View.VISIBLE);
|
|
mStatusSep.setVisibility(View.VISIBLE);
|
|
mStatus2.setVisibility(View.VISIBLE);
|
|
} else {
|
|
// nothing specific to show; show general instructions
|
|
mStatus1.setText(R.string.lockscreen_pattern_instructions);
|
|
mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0);
|
|
|
|
mStatus1.setVisibility(View.VISIBLE);
|
|
mStatusSep.setVisibility(View.GONE);
|
|
mStatus2.setVisibility(View.GONE);
|
|
}
|
|
}
|
|
|
|
|
|
private void refreshTimeAndDateDisplay() {
|
|
Date now = new Date();
|
|
mDate.setText(DateFormat.getMediumDateFormat(getContext()).format(now));
|
|
}
|
|
|
|
|
|
@Override
|
|
public boolean dispatchTouchEvent(MotionEvent 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 boolean result = super.dispatchTouchEvent(ev);
|
|
if (result &&
|
|
((SystemClock.elapsedRealtime() - mLastPokeTime)
|
|
> (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
|
|
mLastPokeTime = SystemClock.elapsedRealtime();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// ---------- InfoCallback
|
|
|
|
/** {@inheritDoc} */
|
|
public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
|
|
mShowingBatteryInfo = showBatteryInfo;
|
|
mPluggedIn = pluggedIn;
|
|
mBatteryLevel = batteryLevel;
|
|
updateStatusLines();
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void onTimeChanged() {
|
|
refreshTimeAndDateDisplay();
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
|
|
mCarrier.setText(LockScreen.getCarrierString(plmn, spn));
|
|
}
|
|
|
|
// ---------- SimStateCallback
|
|
|
|
/** {@inheritDoc} */
|
|
public void onSimStateChanged(IccCard.State simState) {
|
|
}
|
|
|
|
|
|
|
|
|
|
/** {@inheritDoc} */
|
|
public void onOrientationChange(boolean inPortrait) {
|
|
if (inPortrait != mCreatedInPortrait) {
|
|
mCallback.recreateMe();
|
|
}
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void onKeyboardChange(boolean isKeyboardOpen) {}
|
|
|
|
/** {@inheritDoc} */
|
|
public boolean needsInput() {
|
|
return false;
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void onPause() {
|
|
if (mCountdownTimer != null) {
|
|
mCountdownTimer.cancel();
|
|
mCountdownTimer = null;
|
|
}
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void onResume() {
|
|
// reset header
|
|
resetStatusInfo();
|
|
|
|
// reset lock pattern
|
|
mLockPatternView.enableInput();
|
|
mLockPatternView.setEnabled(true);
|
|
mLockPatternView.clearPattern();
|
|
|
|
// show "forgot pattern?" button if we have an alternate authentication method
|
|
mForgotPatternButton.setVisibility(mCallback.doesFallbackUnlockScreenExist()
|
|
? View.VISIBLE : View.INVISIBLE);
|
|
|
|
// if the user is currently locked out, enforce it.
|
|
long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
|
|
if (deadline != 0) {
|
|
handleAttemptLockout(deadline);
|
|
}
|
|
|
|
// the footer depends on how many total attempts the user has failed
|
|
if (mCallback.isVerifyUnlockOnly()) {
|
|
updateFooter(FooterMode.VerifyUnlocked);
|
|
} else if (mEnableFallback &&
|
|
(mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
|
|
updateFooter(FooterMode.ForgotLockPattern);
|
|
} else {
|
|
updateFooter(FooterMode.Normal);
|
|
}
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void cleanUp() {
|
|
mUpdateMonitor.removeCallback(this);
|
|
}
|
|
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
|
super.onWindowFocusChanged(hasWindowFocus);
|
|
if (hasWindowFocus) {
|
|
// when timeout dialog closes we want to update our state
|
|
onResume();
|
|
}
|
|
}
|
|
|
|
private class UnlockPatternListener
|
|
implements LockPatternView.OnPatternListener {
|
|
|
|
public void onPatternStart() {
|
|
mLockPatternView.removeCallbacks(mCancelPatternRunnable);
|
|
}
|
|
|
|
public void onPatternCleared() {
|
|
}
|
|
|
|
public void onPatternCellAdded(List<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.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
|
|
}
|
|
}
|
|
|
|
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
|
|
if (mLockPatternUtils.checkPattern(pattern)) {
|
|
mLockPatternView
|
|
.setDisplayMode(LockPatternView.DisplayMode.Correct);
|
|
mInstructions = "";
|
|
updateStatusLines();
|
|
mCallback.keyguardDone(true);
|
|
} else {
|
|
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
|
|
mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
|
|
}
|
|
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
|
|
if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
|
|
mTotalFailedPatternAttempts++;
|
|
mFailedPatternAttemptsSinceLastTimeout++;
|
|
mCallback.reportFailedPatternAttempt();
|
|
}
|
|
if (mFailedPatternAttemptsSinceLastTimeout >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
|
|
long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
|
|
handleAttemptLockout(deadline);
|
|
return;
|
|
}
|
|
// TODO mUnlockIcon.setVisibility(View.VISIBLE);
|
|
mInstructions = getContext().getString(R.string.lockscreen_pattern_wrong);
|
|
updateStatusLines();
|
|
mLockPatternView.postDelayed(
|
|
mCancelPatternRunnable,
|
|
PATTERN_CLEAR_TIMEOUT_MS);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
|
|
mLockPatternView.clearPattern();
|
|
mLockPatternView.setEnabled(false);
|
|
long elapsedRealtime = SystemClock.elapsedRealtime();
|
|
mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
|
|
|
|
@Override
|
|
public void onTick(long millisUntilFinished) {
|
|
int secondsRemaining = (int) (millisUntilFinished / 1000);
|
|
mInstructions = getContext().getString(
|
|
R.string.lockscreen_too_many_failed_attempts_countdown,
|
|
secondsRemaining);
|
|
updateStatusLines();
|
|
}
|
|
|
|
@Override
|
|
public void onFinish() {
|
|
mLockPatternView.setEnabled(true);
|
|
mInstructions = getContext().getString(R.string.lockscreen_pattern_instructions);
|
|
updateStatusLines();
|
|
// TODO mUnlockIcon.setVisibility(View.VISIBLE);
|
|
mFailedPatternAttemptsSinceLastTimeout = 0;
|
|
if (mEnableFallback) {
|
|
updateFooter(FooterMode.ForgotLockPattern);
|
|
} else {
|
|
updateFooter(FooterMode.Normal);
|
|
}
|
|
}
|
|
}.start();
|
|
}
|
|
}
|