blob: 5245c7cafa0df3b49903f9c725e84263cc1940ca [file] [log] [blame]
/*
* 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.content.ServiceConnection;
import android.os.CountDownTimer;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.ImageView;
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 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;
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 final boolean mEnableFallback;
private boolean mCreatedInPortrait;
private TextView mCarrier;
private TextView mCenterDot;
private TextView mDate;
private TextView mTime;
// 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,
boolean enableFallback) {
super(context);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = updateMonitor;
mCallback = callback;
mTotalFailedPatternAttempts = totalFailedAttempts;
mEnableFallback = enableFallback;
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);
mCenterDot = (TextView) findViewById(R.id.centerDot);
mDate = (TextView) findViewById(R.id.date);
mTime = (TextView) findViewById(R.id.time);
mCenterDot.setText("|");
refreshTimeAndDateDisplay();
mStatus1 = (TextView) findViewById(R.id.status1);
mStatusSep = (TextView) findViewById(R.id.statusSep);
mStatus2 = (TextView) findViewById(R.id.status2);
mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo();
mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
mBatteryLevel = mUpdateMonitor.getBatteryLevel();
mNextAlarm = mLockPatternUtils.getNextAlarm();
updateStatusLines();
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()));
}
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();
mTime.setText(DateFormat.getTimeFormat(getContext()).format(now));
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();
mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
}
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
updateStatusLines();
// TODO mUnlockIcon.setVisibility(View.VISIBLE);
// 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 onPatternDetected(List<LockPatternView.Cell> pattern) {
if (mLockPatternUtils.checkPattern(pattern)) {
mLockPatternView
.setDisplayMode(LockPatternView.DisplayMode.Correct);
mInstructions = "";
updateStatusLines();
mCallback.keyguardDone(true);
} else {
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();
}
}