blob: e8fb7fdd924a02c8870dfde7ed564f192fd0eadd [file] [log] [blame]
/*
* Copyright (C) 2007 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.accounts.AccountManager;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.SystemProperties;
import com.android.internal.telephony.SimCard;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.ColorFilter;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
/**
* The host view for all of the screens of the pattern unlock screen. There are
* two {@link Mode}s of operation, lock and unlock. This will show the appropriate
* screen, and listen for callbacks via {@link com.android.internal.policy.impl.KeyguardScreenCallback
* from the current screen.
*
* This view, in turn, communicates back to {@link com.android.internal.policy.impl.KeyguardViewManager}
* via its {@link com.android.internal.policy.impl.KeyguardViewCallback}, as appropriate.
*/
public class LockPatternKeyguardView extends KeyguardViewBase {
// intent action for launching emergency dialer activity.
static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
private static final boolean DEBUG = false;
private static final String TAG = "LockPatternKeyguardView";
private final KeyguardUpdateMonitor mUpdateMonitor;
private final KeyguardWindowController mWindowController;
private View mLockScreen;
private View mUnlockScreen;
private boolean mScreenOn = false;
private boolean mHasAccount = false; // assume they don't have an account until we know better
/**
* The current {@link KeyguardScreen} will use this to communicate back to us.
*/
KeyguardScreenCallback mKeyguardScreenCallback;
private boolean mRequiresSim;
/**
* Either a lock screen (an informational keyguard screen), or an unlock
* screen (a means for unlocking the device) is shown at any given time.
*/
enum Mode {
LockScreen,
UnlockScreen
}
/**
* The different types screens available for {@link Mode#UnlockScreen}.
* @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode()
*/
enum UnlockMode {
/**
* Unlock by drawing a pattern.
*/
Pattern,
/**
* Unlock by entering a sim pin.
*/
SimPin,
/**
* Unlock by entering an account's login and password.
*/
Account
}
/**
* The current mode.
*/
private Mode mMode = Mode.LockScreen;
/**
* Keeps track of what mode the current unlock screen is
*/
private UnlockMode mUnlockScreenMode;
/**
* If true, it means we are in the process of verifying that the user
* can get past the lock screen per {@link #verifyUnlock()}
*/
private boolean mIsVerifyUnlockOnly = false;
/**
* Used to lookup the state of the lock pattern
*/
private final LockPatternUtils mLockPatternUtils;
/**
* @return Whether we are stuck on the lock screen because the sim is
* missing.
*/
private boolean stuckOnLockScreenBecauseSimMissing() {
return mRequiresSim
&& (!mUpdateMonitor.isDeviceProvisioned())
&& (mUpdateMonitor.getSimState() == SimCard.State.ABSENT);
}
/**
* @param context Used to inflate, and create views.
* @param updateMonitor Knows the state of the world, and passed along to each
* screen so they can use the knowledge, and also register for callbacks
* on dynamic information.
* @param lockPatternUtils Used to look up state of lock pattern.
*/
public LockPatternKeyguardView(
Context context,
KeyguardUpdateMonitor updateMonitor,
LockPatternUtils lockPatternUtils,
KeyguardWindowController controller) {
super(context);
AccountManager accountManager =
(AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE);
mHasAccount = accountManager.blockingGetAccounts().length > 0;
mRequiresSim =
TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim"));
mUpdateMonitor = updateMonitor;
mLockPatternUtils = lockPatternUtils;
mWindowController = controller;
mMode = getInitialMode();
mKeyguardScreenCallback = new KeyguardScreenCallback() {
public void goToLockScreen() {
if (mIsVerifyUnlockOnly) {
// navigating away from unlock screen during verify mode means
// we are done and the user failed to authenticate.
mIsVerifyUnlockOnly = false;
getCallback().keyguardDone(false);
} else {
updateScreen(Mode.LockScreen);
}
}
public void goToUnlockScreen() {
final SimCard.State simState = mUpdateMonitor.getSimState();
if (stuckOnLockScreenBecauseSimMissing()
|| (simState == SimCard.State.PUK_REQUIRED)){
// stuck on lock screen when sim missing or puk'd
return;
}
if (!isSecure()) {
getCallback().keyguardDone(true);
} else {
updateScreen(Mode.UnlockScreen);
}
}
public boolean isSecure() {
return LockPatternKeyguardView.this.isSecure();
}
public boolean isVerifyUnlockOnly() {
return mIsVerifyUnlockOnly;
}
public void recreateMe() {
recreateScreens();
}
public void takeEmergencyCallAction() {
Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
getContext().startActivity(intent);
}
public void pokeWakelock() {
getCallback().pokeWakelock();
}
public void pokeWakelock(int millis) {
getCallback().pokeWakelock(millis);
}
public void keyguardDone(boolean authenticated) {
getCallback().keyguardDone(authenticated);
}
public void keyguardDoneDrawing() {
// irrelevant to keyguard screen, they shouldn't be calling this
}
public void reportFailedPatternAttempt() {
mUpdateMonitor.reportFailedAttempt();
final int failedAttempts = mUpdateMonitor.getFailedAttempts();
if (mHasAccount && failedAttempts ==
(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
- LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
showAlmostAtAccountLoginDialog();
} else if (mHasAccount
&& failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
mLockPatternUtils.setPermanentlyLocked(true);
updateScreen(mMode);
} else if ((failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)
== 0) {
showTimeoutDialog();
}
}
public boolean doesFallbackUnlockScreenExist() {
return mHasAccount;
}
};
/**
* We'll get key events the current screen doesn't use. see
* {@link KeyguardViewBase#onKeyDown(int, android.view.KeyEvent)}
*/
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
// wall paper background
final BitmapDrawable drawable = (BitmapDrawable) context.getWallpaper();
setBackgroundDrawable(
new FastBitmapDrawable(drawable.getBitmap()));
// create both the lock and unlock screen so they are quickly available
// when the screen turns on
mLockScreen = createLockScreen();
addView(mLockScreen);
final UnlockMode unlockMode = getUnlockMode();
mUnlockScreen = createUnlockScreenFor(unlockMode);
mUnlockScreenMode = unlockMode;
addView(mUnlockScreen);
updateScreen(mMode);
}
@Override
public void reset() {
mIsVerifyUnlockOnly = false;
updateScreen(getInitialMode());
}
@Override
public void onScreenTurnedOff() {
mScreenOn = false;
if (mMode == Mode.LockScreen) {
((KeyguardScreen) mLockScreen).onPause();
} else {
((KeyguardScreen) mUnlockScreen).onPause();
}
}
@Override
public void onScreenTurnedOn() {
mScreenOn = true;
if (mMode == Mode.LockScreen) {
((KeyguardScreen) mLockScreen).onResume();
} else {
((KeyguardScreen) mUnlockScreen).onResume();
}
}
private void recreateScreens() {
if (mLockScreen.getVisibility() == View.VISIBLE) {
((KeyguardScreen) mLockScreen).onPause();
}
((KeyguardScreen) mLockScreen).cleanUp();
removeViewInLayout(mLockScreen);
mLockScreen = createLockScreen();
mLockScreen.setVisibility(View.INVISIBLE);
addView(mLockScreen);
if (mUnlockScreen.getVisibility() == View.VISIBLE) {
((KeyguardScreen) mUnlockScreen).onPause();
}
((KeyguardScreen) mUnlockScreen).cleanUp();
removeViewInLayout(mUnlockScreen);
final UnlockMode unlockMode = getUnlockMode();
mUnlockScreen = createUnlockScreenFor(unlockMode);
mUnlockScreen.setVisibility(View.INVISIBLE);
mUnlockScreenMode = unlockMode;
addView(mUnlockScreen);
updateScreen(mMode);
}
@Override
public void wakeWhenReadyTq(int keyCode) {
if (DEBUG) Log.d(TAG, "onWakeKey");
if (keyCode == KeyEvent.KEYCODE_MENU && isSecure() && (mMode == Mode.LockScreen)
&& (mUpdateMonitor.getSimState() != SimCard.State.PUK_REQUIRED)) {
if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
updateScreen(Mode.UnlockScreen);
getCallback().pokeWakelock();
} else {
if (DEBUG) Log.d(TAG, "poking wake lock immediately");
getCallback().pokeWakelock();
}
}
@Override
public void verifyUnlock() {
if (!isSecure()) {
// non-secure keyguard screens are successfull by default
getCallback().keyguardDone(true);
} else if (mUnlockScreenMode != UnlockMode.Pattern) {
// can only verify unlock when in pattern mode
getCallback().keyguardDone(false);
} else {
// otherwise, go to the unlock screen, see if they can verify it
mIsVerifyUnlockOnly = true;
updateScreen(Mode.UnlockScreen);
}
}
@Override
public void cleanUp() {
((KeyguardScreen) mLockScreen).onPause();
((KeyguardScreen) mLockScreen).cleanUp();
((KeyguardScreen) mUnlockScreen).onPause();
((KeyguardScreen) mUnlockScreen).cleanUp();
}
private boolean isSecure() {
UnlockMode unlockMode = getUnlockMode();
if (unlockMode == UnlockMode.Pattern) {
return mLockPatternUtils.isLockPatternEnabled();
} else if (unlockMode == UnlockMode.SimPin) {
return mUpdateMonitor.getSimState() == SimCard.State.PIN_REQUIRED
|| mUpdateMonitor.getSimState() == SimCard.State.PUK_REQUIRED;
} else if (unlockMode == UnlockMode.Account) {
return true;
} else {
throw new IllegalStateException("unknown unlock mode " + unlockMode);
}
}
private void updateScreen(final Mode mode) {
mMode = mode;
final View goneScreen = (mode == Mode.LockScreen) ? mUnlockScreen : mLockScreen;
final View visibleScreen = (mode == Mode.LockScreen)
? mLockScreen : getUnlockScreenForCurrentUnlockMode();
// do this before changing visibility so focus isn't requested before the input
// flag is set
mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput());
if (mScreenOn) {
if (goneScreen.getVisibility() == View.VISIBLE) {
((KeyguardScreen) goneScreen).onPause();
}
if (visibleScreen.getVisibility() != View.VISIBLE) {
((KeyguardScreen) visibleScreen).onResume();
}
}
goneScreen.setVisibility(View.GONE);
visibleScreen.setVisibility(View.VISIBLE);
if (!visibleScreen.requestFocus()) {
throw new IllegalStateException("keyguard screen must be able to take "
+ "focus when shown " + visibleScreen.getClass().getCanonicalName());
}
}
View createLockScreen() {
return new LockScreen(
mContext,
mLockPatternUtils,
mUpdateMonitor,
mKeyguardScreenCallback);
}
View createUnlockScreenFor(UnlockMode unlockMode) {
if (unlockMode == UnlockMode.Pattern) {
return new UnlockScreen(
mContext,
mLockPatternUtils,
mUpdateMonitor,
mKeyguardScreenCallback,
mUpdateMonitor.getFailedAttempts());
} else if (unlockMode == UnlockMode.SimPin) {
return new SimUnlockScreen(
mContext,
mUpdateMonitor,
mKeyguardScreenCallback);
} else if (unlockMode == UnlockMode.Account) {
try {
return new AccountUnlockScreen(
mContext,
mKeyguardScreenCallback,
mLockPatternUtils);
} catch (IllegalStateException e) {
Log.i(TAG, "Couldn't instantiate AccountUnlockScreen"
+ " (IAccountsService isn't available)");
// TODO: Need a more general way to provide a
// platform-specific fallback UI here.
// For now, if we can't display the account login
// unlock UI, just bring back the regular "Pattern" unlock mode.
// (We do this by simply returning a regular UnlockScreen
// here. This means that the user will still see the
// regular pattern unlock UI, regardless of the value of
// mUnlockScreenMode or whether or not we're in the
// "permanently locked" state.)
return createUnlockScreenFor(UnlockMode.Pattern);
}
} else {
throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
}
}
private View getUnlockScreenForCurrentUnlockMode() {
final UnlockMode unlockMode = getUnlockMode();
// if a screen exists for the correct mode, we're done
if (unlockMode == mUnlockScreenMode) {
return mUnlockScreen;
}
// remember the mode
mUnlockScreenMode = unlockMode;
// unlock mode has changed and we have an existing old unlock screen
// to clean up
if (mScreenOn && (mUnlockScreen.getVisibility() == View.VISIBLE)) {
((KeyguardScreen) mUnlockScreen).onPause();
}
((KeyguardScreen) mUnlockScreen).cleanUp();
removeViewInLayout(mUnlockScreen);
// create the new one
mUnlockScreen = createUnlockScreenFor(unlockMode);
mUnlockScreen.setVisibility(View.INVISIBLE);
addView(mUnlockScreen);
return mUnlockScreen;
}
/**
* Given the current state of things, what should be the initial mode of
* the lock screen (lock or unlock).
*/
private Mode getInitialMode() {
final SimCard.State simState = mUpdateMonitor.getSimState();
if (stuckOnLockScreenBecauseSimMissing() || (simState == SimCard.State.PUK_REQUIRED)) {
return Mode.LockScreen;
} else if (mUpdateMonitor.isKeyboardOpen() && isSecure()) {
return Mode.UnlockScreen;
} else {
return Mode.LockScreen;
}
}
/**
* Given the current state of things, what should the unlock screen be?
*/
private UnlockMode getUnlockMode() {
final SimCard.State simState = mUpdateMonitor.getSimState();
if (simState == SimCard.State.PIN_REQUIRED || simState == SimCard.State.PUK_REQUIRED) {
return UnlockMode.SimPin;
} else {
return mLockPatternUtils.isPermanentlyLocked() ?
UnlockMode.Account:
UnlockMode.Pattern;
}
}
private void showTimeoutDialog() {
int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
String message = mContext.getString(
R.string.lockscreen_too_many_failed_attempts_dialog_message,
mUpdateMonitor.getFailedAttempts(),
timeoutInSeconds);
final AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(null)
.setMessage(message)
.setNeutralButton(R.string.ok, null)
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
dialog.show();
}
private void showAlmostAtAccountLoginDialog() {
int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
String message = mContext.getString(
R.string.lockscreen_failed_attempts_almost_glogin,
LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET - LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT,
timeoutInSeconds);
final AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(null)
.setMessage(message)
.setNeutralButton(R.string.ok, null)
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
dialog.show();
}
/**
* Used to put wallpaper on the background of the lock screen. Centers it Horizontally and
* vertically.
*/
static private class FastBitmapDrawable extends Drawable {
private Bitmap mBitmap;
private FastBitmapDrawable(Bitmap bitmap) {
mBitmap = bitmap;
}
@Override
public void draw(Canvas canvas) {
canvas.drawBitmap(
mBitmap,
(getBounds().width() - mBitmap.getWidth()) / 2,
(getBounds().height() - mBitmap.getHeight()) / 2,
null);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getIntrinsicWidth() {
return mBitmap.getWidth();
}
@Override
public int getIntrinsicHeight() {
return mBitmap.getHeight();
}
@Override
public int getMinimumWidth() {
return mBitmap.getWidth();
}
@Override
public int getMinimumHeight() {
return mBitmap.getHeight();
}
}
}