blob: 404dc6f5924b8a1a1c52c05fb4e8278a4f4790c1 [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 com.android.internal.R;
import com.android.internal.policy.impl.KeyguardUpdateMonitor.InfoCallback;
import com.android.internal.policy.impl.KeyguardUpdateMonitor.InfoCallbackImpl;
import com.android.internal.policy.impl.LockPatternKeyguardView.UnlockMode;
import com.android.internal.policy.IFaceLockCallback;
import com.android.internal.policy.IFaceLockInterface;
import com.android.internal.telephony.IccCard;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockScreenWidgetCallback;
import com.android.internal.widget.LockScreenWidgetInterface;
import com.android.internal.widget.TransportControlView;
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.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import java.io.IOException;
/**
* 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 implements Handler.Callback {
private static final int TRANSPORT_USERACTIVITY_TIMEOUT = 10000;
static final boolean DEBUG_CONFIGURATION = false;
// time after launching EmergencyDialer before the screen goes blank.
private static final int EMERGENCY_CALL_TIMEOUT = 10000;
// 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;
private boolean mWindowFocused = false;
private boolean mEnableFallback = false; // assume no fallback UI until we know better
private boolean mShowLockBeforeUnlock = false;
// The following were added to support FaceLock
private IFaceLockInterface mFaceLockService;
private boolean mBoundToFaceLockService = false;
private View mFaceLockAreaView;
private boolean mFaceLockServiceRunning = false;
private final Object mFaceLockServiceRunningLock = new Object();
private final Object mFaceLockStartupLock = new Object();
private Handler mHandler;
private final int MSG_SHOW_FACELOCK_AREA_VIEW = 0;
private final int MSG_HIDE_FACELOCK_AREA_VIEW = 1;
// Long enough to stay visible while dialer comes up
// Short enough to not be visible if the user goes back immediately
private final int FACELOCK_VIEW_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
// Long enough to stay visible while the service starts
// Short enough to not have to wait long for backup if service fails to start or crashes
// The service can take a couple of seconds to start on the first try after boot
private final int FACELOCK_VIEW_AREA_SERVICE_TIMEOUT = 3000;
// So the user has a consistent amount of time when brought to the backup method from FaceLock
private final int BACKUP_LOCK_TIMEOUT = 5000;
private boolean mRequiresSim;
//True if we have some sort of overlay on top of the Lockscreen
//Also true if we've activated a phone call, either emergency dialing or incoming
//This resets when the phone is turned off with no current call
private boolean mHasOverlay;
//True if a dialog is currently displaying on top of this window
//Unlike other overlays, this does not close with a power button cycle
private boolean mHasDialog = false;
//True if this device is currently plugged in
private boolean mPluggedIn;
// The music control widget
private TransportControlView mTransportControlView;
private Parcelable mSavedState;
/**
* 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 a sim puk.
*/
SimPuk,
/**
* Unlock by entering an account's login and password.
*/
Account,
/**
* Unlock by entering a password or PIN
*/
Password,
/**
* Unknown (uninitialized) value
*/
Unknown
}
/**
* The current mode.
*/
private Mode mMode = Mode.LockScreen;
/**
* Keeps track of what mode the current unlock screen is (cached from most recent computation in
* {@link #getUnlockMode}).
*/
private UnlockMode mUnlockScreenMode = UnlockMode.Unknown;
private boolean mForgotPattern;
/**
* 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;
/**
* The current configuration.
*/
private Configuration mConfiguration;
private Runnable mRecreateRunnable = new Runnable() {
public void run() {
updateScreen(mMode, true);
restoreWidgetState();
}
};
private LockScreenWidgetCallback mWidgetCallback = new LockScreenWidgetCallback() {
public void userActivity(View self) {
mKeyguardScreenCallback.pokeWakelock(TRANSPORT_USERACTIVITY_TIMEOUT);
}
public void requestShow(View view) {
if (DEBUG) Log.v(TAG, "View " + view + " requested show transports");
view.setVisibility(View.VISIBLE);
// TODO: examine all widgets to derive clock status
mUpdateMonitor.reportClockVisible(false);
// If there's not a bg protection view containing the transport, then show a black
// background. Otherwise, allow the normal background to show.
if (findViewById(R.id.transport_bg_protect) == null) {
// TODO: We should disable the wallpaper instead
setBackgroundColor(0xff000000);
} else {
resetBackground();
}
}
public void requestHide(View view) {
if (DEBUG) Log.v(TAG, "View " + view + " requested hide transports");
view.setVisibility(View.GONE);
// TODO: examine all widgets to derive clock status
mUpdateMonitor.reportClockVisible(true);
resetBackground();
}
public boolean isVisible(View self) {
// TODO: this should be up to the lockscreen to determine if the view
// is currently showing. The idea is it can be used for the widget to
// avoid doing work if it's not visible. For now just returns the view's
// actual visibility.
return self.getVisibility() == View.VISIBLE;
}
};
/**
* @return Whether we are stuck on the lock screen because the sim is
* missing.
*/
private boolean stuckOnLockScreenBecauseSimMissing() {
return mRequiresSim
&& (!mUpdateMonitor.isDeviceProvisioned())
&& (mUpdateMonitor.getSimState() == IccCard.State.ABSENT ||
mUpdateMonitor.getSimState() == IccCard.State.PERM_DISABLED);
}
/**
* The current {@link KeyguardScreen} will use this to communicate back to us.
*/
KeyguardScreenCallback mKeyguardScreenCallback = new KeyguardScreenCallback() {
public void goToLockScreen() {
mForgotPattern = false;
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, false);
}
}
public void goToUnlockScreen() {
final IccCard.State simState = mUpdateMonitor.getSimState();
if (stuckOnLockScreenBecauseSimMissing()
|| (simState == IccCard.State.PUK_REQUIRED
&& !mLockPatternUtils.isPukUnlockScreenEnable())){
// stuck on lock screen when sim missing or
// puk'd but puk unlock screen is disabled
return;
}
if (!isSecure()) {
getCallback().keyguardDone(true);
} else {
updateScreen(Mode.UnlockScreen, false);
}
}
public void forgotPattern(boolean isForgotten) {
if (mEnableFallback) {
mForgotPattern = isForgotten;
updateScreen(Mode.UnlockScreen, false);
}
}
public boolean isSecure() {
return LockPatternKeyguardView.this.isSecure();
}
public boolean isVerifyUnlockOnly() {
return mIsVerifyUnlockOnly;
}
public void recreateMe(Configuration config) {
removeCallbacks(mRecreateRunnable);
post(mRecreateRunnable);
}
public void takeEmergencyCallAction() {
mHasOverlay = true;
// Continue showing FaceLock area until dialer comes up or call is resumed
if (usingFaceLock() && mFaceLockServiceRunning) {
showFaceLockAreaWithTimeout(FACELOCK_VIEW_AREA_EMERGENCY_DIALER_TIMEOUT);
}
// FaceLock must be stopped if it is running when emergency call is pressed
stopAndUnbindFromFaceLock();
pokeWakelock(EMERGENCY_CALL_TIMEOUT);
if (TelephonyManager.getDefault().getCallState()
== TelephonyManager.CALL_STATE_OFFHOOK) {
mLockPatternUtils.resumeCall();
} else {
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);
mSavedState = null; // clear state so we re-establish when locked again
}
public void keyguardDoneDrawing() {
// irrelevant to keyguard screen, they shouldn't be calling this
}
public void reportFailedUnlockAttempt() {
mUpdateMonitor.reportFailedAttempt();
final int failedAttempts = mUpdateMonitor.getFailedAttempts();
if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts +
" (enableFallback=" + mEnableFallback + ")");
final boolean usingPattern = mLockPatternUtils.getKeyguardStoredPasswordQuality()
== DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
final int failedAttemptsBeforeWipe = mLockPatternUtils.getDevicePolicyManager()
.getMaximumFailedPasswordsForWipe(null);
final int failedAttemptWarning = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
- LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
(failedAttemptsBeforeWipe - failedAttempts)
: Integer.MAX_VALUE; // because DPM returns 0 if no restriction
if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
// If we reach this code, it means the user has installed a DevicePolicyManager
// that requests device wipe after N attempts. Once we get below the grace
// period, we'll post this dialog every time as a clear warning until the
// bombshell hits and the device is wiped.
if (remainingBeforeWipe > 0) {
showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe);
} else {
// Too many attempts. The device will be wiped shortly.
Slog.i(TAG, "Too many unlock attempts; device will be wiped!");
showWipeDialog(failedAttempts);
}
} else {
boolean showTimeout =
(failedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) == 0;
if (usingPattern && mEnableFallback) {
if (failedAttempts == failedAttemptWarning) {
showAlmostAtAccountLoginDialog();
showTimeout = false; // don't show both dialogs
} else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
mLockPatternUtils.setPermanentlyLocked(true);
updateScreen(mMode, false);
// don't show timeout dialog because we show account unlock screen next
showTimeout = false;
}
}
if (showTimeout) {
showTimeoutDialog();
}
}
mLockPatternUtils.reportFailedPasswordAttempt();
}
public boolean doesFallbackUnlockScreenExist() {
return mEnableFallback;
}
public void reportSuccessfulUnlockAttempt() {
mLockPatternUtils.reportSuccessfulPasswordAttempt();
}
};
/**
* @param context Used to inflate, and create views.
* @param callback Keyguard callback object for pokewakelock(), etc.
* @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, KeyguardViewCallback callback, KeyguardUpdateMonitor updateMonitor,
LockPatternUtils lockPatternUtils, KeyguardWindowController controller) {
super(context, callback);
mHandler = new Handler(this);
mConfiguration = context.getResources().getConfiguration();
mEnableFallback = false;
mRequiresSim = TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim"));
mUpdateMonitor = updateMonitor;
mLockPatternUtils = lockPatternUtils;
mWindowController = controller;
mHasOverlay = false;
mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
mScreenOn = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isScreenOn();
mUpdateMonitor.registerInfoCallback(mInfoCallback);
/**
* 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);
updateScreen(getInitialMode(), false);
maybeEnableFallback(context);
}
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.getAccountsByType("com.google");
}
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) {
if (mUnlockScreen == null) {
if (DEBUG) Log.w(TAG, "no unlock screen when trying to enable fallback");
} else if (mUnlockScreen instanceof PatternUnlockScreen) {
((PatternUnlockScreen)mUnlockScreen).setEnableFallback(mEnableFallback);
}
return;
}
// lookup the confirmCredentials intent for the current account
mAccountManager.confirmCredentials(mAccounts[mAccountIndex], null, null, this, null);
}
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 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();
}
// TODO:
// This overloaded method was added to workaround a race condition in the framework between
// notification for orientation changed, layout() and switching resources. This code attempts
// to avoid drawing the incorrect layout while things are in transition. The method can just
// be removed once the race condition is fixed. See bugs 2262578 and 2292713.
@Override
protected void dispatchDraw(Canvas canvas) {
if (DEBUG) Log.v(TAG, "*** dispatchDraw() time: " + SystemClock.elapsedRealtime());
super.dispatchDraw(canvas);
}
@Override
public void reset() {
mIsVerifyUnlockOnly = false;
mForgotPattern = false;
post(mRecreateRunnable);
}
@Override
public void onScreenTurnedOff() {
if (DEBUG) Log.d(TAG, "screen off");
mScreenOn = false;
mForgotPattern = false;
mHasOverlay = mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE ||
mHasDialog;
// Emulate activity life-cycle for both lock and unlock screen.
if (mLockScreen != null) {
((KeyguardScreen) mLockScreen).onPause();
}
if (mUnlockScreen != null) {
((KeyguardScreen) mUnlockScreen).onPause();
}
saveWidgetState();
// When screen is turned off, need to unbind from FaceLock service if using FaceLock
stopAndUnbindFromFaceLock();
}
/** When screen is turned on and focused, need to bind to FaceLock service if we are using
* FaceLock, but only if we're not dealing with a call
*/
private void activateFaceLockIfAble() {
final boolean tooManyFaceUnlockTries = mUpdateMonitor.getMaxFaceUnlockAttemptsReached();
final int failedBackupAttempts = mUpdateMonitor.getFailedAttempts();
final boolean backupIsTimedOut =
(failedBackupAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
if (tooManyFaceUnlockTries) Log.i(TAG, "tooManyFaceUnlockTries: " + tooManyFaceUnlockTries);
if (mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
&& usingFaceLock()
&& !mHasOverlay
&& !tooManyFaceUnlockTries
&& !backupIsTimedOut) {
bindToFaceLock();
// Show FaceLock area, but only for a little bit so lockpattern will become visible if
// FaceLock fails to start or crashes
showFaceLockAreaWithTimeout(FACELOCK_VIEW_AREA_SERVICE_TIMEOUT);
// When switching between portrait and landscape view while FaceLock is running, the
// screen will eventually go dark unless we poke the wakelock when FaceLock is
// restarted
mKeyguardScreenCallback.pokeWakelock();
} else {
hideFaceLockArea();
}
}
@Override
public void onScreenTurnedOn() {
if (DEBUG) Log.d(TAG, "screen on");
boolean runFaceLock = false;
//Make sure to start facelock iff the screen is both on and focused
synchronized(mFaceLockStartupLock) {
mScreenOn = true;
runFaceLock = mWindowFocused;
}
show();
restoreWidgetState();
if (runFaceLock) activateFaceLockIfAble();
}
private void saveWidgetState() {
if (mTransportControlView != null) {
if (DEBUG) Log.v(TAG, "Saving widget state");
mSavedState = mTransportControlView.onSaveInstanceState();
}
}
private void restoreWidgetState() {
if (mTransportControlView != null) {
if (DEBUG) Log.v(TAG, "Restoring widget state");
if (mSavedState != null) {
mTransportControlView.onRestoreInstanceState(mSavedState);
}
}
}
/** Unbind from facelock if something covers this window (such as an alarm)
* bind to facelock if the lockscreen window just came into focus, and the screen is on
*/
@Override
public void onWindowFocusChanged (boolean hasWindowFocus) {
if (DEBUG) Log.d(TAG, hasWindowFocus ? "focused" : "unfocused");
boolean runFaceLock = false;
//Make sure to start facelock iff the screen is both on and focused
synchronized(mFaceLockStartupLock) {
if(mScreenOn && !mWindowFocused) runFaceLock = hasWindowFocus;
mWindowFocused = hasWindowFocus;
}
if(!hasWindowFocus) {
mHasOverlay = true;
stopAndUnbindFromFaceLock();
hideFaceLockArea();
} else {
mHasDialog = false;
if (runFaceLock) activateFaceLockIfAble();
}
}
@Override
public void show() {
// Emulate activity life-cycle for both lock and unlock screen.
if (mLockScreen != null) {
((KeyguardScreen) mLockScreen).onResume();
}
if (mUnlockScreen != null) {
((KeyguardScreen) mUnlockScreen).onResume();
}
if (usingFaceLock() && !mHasOverlay) {
// Note that show() gets called before the screen turns off to set it up for next time
// it is turned on. We don't want to set a timeout on the FaceLock area here because it
// may be gone by the time the screen is turned on again. We set the timeout when the
// screen turns on instead.
showFaceLockArea();
} else {
hideFaceLockArea();
}
}
private void recreateLockScreen() {
if (mLockScreen != null) {
((KeyguardScreen) mLockScreen).onPause();
((KeyguardScreen) mLockScreen).cleanUp();
removeView(mLockScreen);
}
mLockScreen = createLockScreen();
mLockScreen.setVisibility(View.INVISIBLE);
addView(mLockScreen);
}
private void recreateUnlockScreen(UnlockMode unlockMode) {
if (mUnlockScreen != null) {
((KeyguardScreen) mUnlockScreen).onPause();
((KeyguardScreen) mUnlockScreen).cleanUp();
removeView(mUnlockScreen);
}
mUnlockScreen = createUnlockScreenFor(unlockMode);
mUnlockScreen.setVisibility(View.INVISIBLE);
addView(mUnlockScreen);
}
@Override
protected void onDetachedFromWindow() {
mUpdateMonitor.removeCallback(mInfoCallback);
removeCallbacks(mRecreateRunnable);
// When view is hidden, need to unbind from FaceLock service if we are using FaceLock
// e.g., when device becomes unlocked
stopAndUnbindFromFaceLock();
super.onDetachedFromWindow();
}
protected void onConfigurationChanged(Configuration newConfig) {
Resources resources = getResources();
mShowLockBeforeUnlock = resources.getBoolean(R.bool.config_enableLockBeforeUnlockScreen);
mConfiguration = newConfig;
if (DEBUG_CONFIGURATION) Log.v(TAG, "**** re-creating lock screen since config changed");
saveWidgetState();
removeCallbacks(mRecreateRunnable);
post(mRecreateRunnable);
}
InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() {
/** When somebody plugs in or unplugs the device, we don't want to display faceunlock */
@Override
public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn,
int batteryLevel) {
mHasOverlay |= mPluggedIn != pluggedIn;
mPluggedIn = pluggedIn;
//If it's already running, don't close it down: the unplug didn't start it
if (!mFaceLockServiceRunning) {
stopAndUnbindFromFaceLock();
hideFaceLockArea();
}
}
@Override
public void onClockVisibilityChanged() {
int visFlags = getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK;
setSystemUiVisibility(visFlags
| (mUpdateMonitor.isClockVisible() ? View.STATUS_BAR_DISABLE_CLOCK : 0));
}
//We need to stop faceunlock when a phonecall comes in
@Override
public void onPhoneStateChanged(int phoneState) {
if (DEBUG) Log.d(TAG, "phone state: " + phoneState);
if(phoneState == TelephonyManager.CALL_STATE_RINGING) {
mHasOverlay = true;
stopAndUnbindFromFaceLock();
hideFaceLockArea();
}
}
@Override
public void onUserChanged(int userId) {
mLockPatternUtils.setCurrentUser(userId);
updateScreen(getInitialMode(), true);
}
};
@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
// Do not let the screen to get locked while the user is disabled and touch
// exploring. A blind user will need significantly more time to find and
// interact with the lock screen views.
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
if (accessibilityManager.isEnabled() && accessibilityManager.isTouchExplorationEnabled()) {
getCallback().pokeWakelock();
}
return super.dispatchHoverEvent(event);
}
@Override
public void wakeWhenReadyTq(int keyCode) {
if (DEBUG) Log.d(TAG, "onWakeKey");
if (keyCode == KeyEvent.KEYCODE_MENU && isSecure() && (mMode == Mode.LockScreen)
&& (mUpdateMonitor.getSimState() != IccCard.State.PUK_REQUIRED)) {
if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
updateScreen(Mode.UnlockScreen, false);
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
&& mUnlockScreenMode != UnlockMode.Password) {
// can only verify unlock when in pattern/password mode
getCallback().keyguardDone(false);
} else {
// otherwise, go to the unlock screen, see if they can verify it
mIsVerifyUnlockOnly = true;
updateScreen(Mode.UnlockScreen, false);
}
}
@Override
public void cleanUp() {
if (mLockScreen != null) {
((KeyguardScreen) mLockScreen).onPause();
((KeyguardScreen) mLockScreen).cleanUp();
this.removeView(mLockScreen);
mLockScreen = null;
}
if (mUnlockScreen != null) {
((KeyguardScreen) mUnlockScreen).onPause();
((KeyguardScreen) mUnlockScreen).cleanUp();
this.removeView(mUnlockScreen);
mUnlockScreen = null;
}
mUpdateMonitor.removeCallback(this);
if (mFaceLockService != null) {
try {
mFaceLockService.unregisterCallback(mFaceLockCallback);
} catch (RemoteException e) {
// Not much we can do
}
stopFaceLock();
mFaceLockService = null;
}
}
private boolean isSecure() {
UnlockMode unlockMode = getUnlockMode();
boolean secure = false;
switch (unlockMode) {
case Pattern:
secure = mLockPatternUtils.isLockPatternEnabled();
break;
case SimPin:
secure = mUpdateMonitor.getSimState() == IccCard.State.PIN_REQUIRED;
break;
case SimPuk:
secure = mUpdateMonitor.getSimState() == IccCard.State.PUK_REQUIRED;
break;
case Account:
secure = true;
break;
case Password:
secure = mLockPatternUtils.isLockPasswordEnabled();
break;
case Unknown:
// This means no security is set up
break;
default:
throw new IllegalStateException("unknown unlock mode " + unlockMode);
}
return secure;
}
private void updateScreen(Mode mode, boolean force) {
if (DEBUG_CONFIGURATION) Log.v(TAG, "**** UPDATE SCREEN: mode=" + mode
+ " last mode=" + mMode + ", force = " + force, new RuntimeException());
mMode = mode;
// Re-create the lock screen if necessary
if (mode == Mode.LockScreen || mShowLockBeforeUnlock) {
if (force || mLockScreen == null) {
recreateLockScreen();
}
}
// Re-create the unlock screen if necessary. This is primarily required to properly handle
// SIM state changes. This typically happens when this method is called by reset()
final UnlockMode unlockMode = getUnlockMode();
if (mode == Mode.UnlockScreen && unlockMode != UnlockMode.Unknown) {
if (force || mUnlockScreen == null || unlockMode != mUnlockScreenMode) {
boolean restartFaceLock = stopFaceLockIfRunning();
recreateUnlockScreen(unlockMode);
if (restartFaceLock) activateFaceLockIfAble();
}
}
// visibleScreen should never be null
final View goneScreen = (mode == Mode.LockScreen) ? mUnlockScreen : mLockScreen;
final View visibleScreen = (mode == Mode.LockScreen) ? mLockScreen : mUnlockScreen;
// do this before changing visibility so focus isn't requested before the input
// flag is set
mWindowController.setNeedsInput(((KeyguardScreen)visibleScreen).needsInput());
if (DEBUG_CONFIGURATION) {
Log.v(TAG, "Gone=" + goneScreen);
Log.v(TAG, "Visible=" + visibleScreen);
}
if (mScreenOn) {
if (goneScreen != null && goneScreen.getVisibility() == View.VISIBLE) {
((KeyguardScreen) goneScreen).onPause();
}
if (visibleScreen.getVisibility() != View.VISIBLE) {
((KeyguardScreen) visibleScreen).onResume();
}
}
if (goneScreen != null) {
goneScreen.setVisibility(View.GONE);
}
visibleScreen.setVisibility(View.VISIBLE);
requestLayout();
if (!visibleScreen.requestFocus()) {
throw new IllegalStateException("keyguard screen must be able to take "
+ "focus when shown " + visibleScreen.getClass().getCanonicalName());
}
}
View createLockScreen() {
View lockView = new LockScreen(
mContext,
mConfiguration,
mLockPatternUtils,
mUpdateMonitor,
mKeyguardScreenCallback);
initializeTransportControlView(lockView);
return lockView;
}
View createUnlockScreenFor(UnlockMode unlockMode) {
View unlockView = null;
if (DEBUG) Log.d(TAG,
"createUnlockScreenFor(" + unlockMode + "): mEnableFallback=" + mEnableFallback);
if (unlockMode == UnlockMode.Pattern) {
PatternUnlockScreen view = new PatternUnlockScreen(
mContext,
mConfiguration,
mLockPatternUtils,
mUpdateMonitor,
mKeyguardScreenCallback,
mUpdateMonitor.getFailedAttempts());
view.setEnableFallback(mEnableFallback);
unlockView = view;
} else if (unlockMode == UnlockMode.SimPuk) {
unlockView = new SimPukUnlockScreen(
mContext,
mConfiguration,
mUpdateMonitor,
mKeyguardScreenCallback,
mLockPatternUtils);
} else if (unlockMode == UnlockMode.SimPin) {
unlockView = new SimUnlockScreen(
mContext,
mConfiguration,
mUpdateMonitor,
mKeyguardScreenCallback,
mLockPatternUtils);
} else if (unlockMode == UnlockMode.Account) {
try {
unlockView = new AccountUnlockScreen(
mContext,
mConfiguration,
mUpdateMonitor,
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 if (unlockMode == UnlockMode.Password) {
unlockView = new PasswordUnlockScreen(
mContext,
mConfiguration,
mLockPatternUtils,
mUpdateMonitor,
mKeyguardScreenCallback);
} else {
throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
}
initializeTransportControlView(unlockView);
initializeFaceLockAreaView(unlockView); // Only shows view if FaceLock is enabled
mUnlockScreenMode = unlockMode;
return unlockView;
}
private void initializeTransportControlView(View view) {
mTransportControlView = (TransportControlView) view.findViewById(R.id.transport);
if (mTransportControlView == null) {
if (DEBUG) Log.w(TAG, "Couldn't find transport control widget");
} else {
mUpdateMonitor.reportClockVisible(true);
mTransportControlView.setVisibility(View.GONE); // hide until it requests being shown.
mTransportControlView.setCallback(mWidgetCallback);
}
}
/**
* Given the current state of things, what should be the initial mode of
* the lock screen (lock or unlock).
*/
private Mode getInitialMode() {
final IccCard.State simState = mUpdateMonitor.getSimState();
if (stuckOnLockScreenBecauseSimMissing() ||
(simState == IccCard.State.PUK_REQUIRED &&
!mLockPatternUtils.isPukUnlockScreenEnable())) {
return Mode.LockScreen;
} else {
if (!isSecure() || mShowLockBeforeUnlock) {
return Mode.LockScreen;
} else {
return Mode.UnlockScreen;
}
}
}
/**
* Given the current state of things, what should the unlock screen be?
*/
private UnlockMode getUnlockMode() {
final IccCard.State simState = mUpdateMonitor.getSimState();
UnlockMode currentMode;
if (simState == IccCard.State.PIN_REQUIRED) {
currentMode = UnlockMode.SimPin;
} else if (simState == IccCard.State.PUK_REQUIRED) {
currentMode = UnlockMode.SimPuk;
} else {
final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality();
switch (mode) {
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
currentMode = UnlockMode.Password;
break;
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
if (mLockPatternUtils.isLockPatternEnabled()) {
// "forgot pattern" button is only available in the pattern mode...
if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) {
currentMode = UnlockMode.Account;
} else {
currentMode = UnlockMode.Pattern;
}
} else {
currentMode = UnlockMode.Unknown;
}
break;
default:
throw new IllegalStateException("Unknown unlock mode:" + mode);
}
}
return currentMode;
}
private void showDialog(String title, String message) {
mHasDialog = true;
final AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(title)
.setMessage(message)
.setNeutralButton(R.string.ok, null)
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.show();
}
private void showTimeoutDialog() {
int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
int messageId = R.string.lockscreen_too_many_failed_attempts_dialog_message;
if (getUnlockMode() == UnlockMode.Password) {
if(mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
messageId = R.string.lockscreen_too_many_failed_pin_attempts_dialog_message;
} else {
messageId = R.string.lockscreen_too_many_failed_password_attempts_dialog_message;
}
}
String message = mContext.getString(messageId, mUpdateMonitor.getFailedAttempts(),
timeoutInSeconds);
showDialog(null, message);
}
private void showAlmostAtAccountLoginDialog() {
final int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
final int count = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET
- LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
String message = mContext.getString(R.string.lockscreen_failed_attempts_almost_glogin,
count, LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT, timeoutInSeconds);
showDialog(null, message);
}
private void showAlmostAtWipeDialog(int attempts, int remaining) {
int timeoutInSeconds = (int) LockPatternUtils.FAILED_ATTEMPT_TIMEOUT_MS / 1000;
String message = mContext.getString(
R.string.lockscreen_failed_attempts_almost_at_wipe, attempts, remaining);
showDialog(null, message);
}
private void showWipeDialog(int attempts) {
String message = mContext.getString(
R.string.lockscreen_failed_attempts_now_wiping, attempts);
showDialog(null, message);
}
/**
* Used to put wallpaper on the background of the lock screen. Centers it
* Horizontally and pins the bottom (assuming that the lock screen is aligned
* with the bottom, so the wallpaper should extend above the top into the
* status bar).
*/
static private class FastBitmapDrawable extends Drawable {
private Bitmap mBitmap;
private int mOpacity;
private FastBitmapDrawable(Bitmap bitmap) {
mBitmap = bitmap;
mOpacity = mBitmap.hasAlpha() ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
}
@Override
public void draw(Canvas canvas) {
canvas.drawBitmap(
mBitmap,
(getBounds().width() - mBitmap.getWidth()) / 2,
(getBounds().height() - mBitmap.getHeight()),
null);
}
@Override
public int getOpacity() {
return mOpacity;
}
@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();
}
}
// Everything below pertains to FaceLock - might want to separate this out
// Indicates whether FaceLock is in use
private boolean usingFaceLock() {
return (mLockPatternUtils.usingBiometricWeak() &&
mLockPatternUtils.isBiometricWeakInstalled());
}
// Takes care of FaceLock area when layout is created
private void initializeFaceLockAreaView(View view) {
if (usingFaceLock()) {
mFaceLockAreaView = view.findViewById(R.id.faceLockAreaView);
if (mFaceLockAreaView == null) {
Log.e(TAG, "Layout does not have faceLockAreaView and FaceLock is enabled");
}
} else {
mFaceLockAreaView = null; // Set to null if not using FaceLock
}
}
// Stops FaceLock if it is running and reports back whether it was running or not
private boolean stopFaceLockIfRunning() {
if (usingFaceLock() && mBoundToFaceLockService) {
stopAndUnbindFromFaceLock();
return true;
}
return false;
}
// Handles covering or exposing FaceLock area on the client side when FaceLock starts or stops
// This needs to be done in a handler because the call could be coming from a callback from the
// FaceLock service that is in a thread that can't modify the UI
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_SHOW_FACELOCK_AREA_VIEW:
if (mFaceLockAreaView != null) {
mFaceLockAreaView.setVisibility(View.VISIBLE);
}
break;
case MSG_HIDE_FACELOCK_AREA_VIEW:
if (mFaceLockAreaView != null) {
mFaceLockAreaView.setVisibility(View.INVISIBLE);
}
break;
default:
Log.w(TAG, "Unhandled message");
return false;
}
return true;
}
// Removes show and hide messages from the message queue
private void removeFaceLockAreaDisplayMessages() {
mHandler.removeMessages(MSG_SHOW_FACELOCK_AREA_VIEW);
mHandler.removeMessages(MSG_HIDE_FACELOCK_AREA_VIEW);
}
// Shows the FaceLock area immediately
private void showFaceLockArea() {
// Remove messages to prevent a delayed hide message from undo-ing the show
removeFaceLockAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_SHOW_FACELOCK_AREA_VIEW);
}
// Hides the FaceLock area immediately
private void hideFaceLockArea() {
// Remove messages to prevent a delayed show message from undo-ing the hide
removeFaceLockAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_HIDE_FACELOCK_AREA_VIEW);
}
// Shows the FaceLock area for a period of time
private void showFaceLockAreaWithTimeout(long timeoutMillis) {
showFaceLockArea();
mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACELOCK_AREA_VIEW, timeoutMillis);
}
// Binds to FaceLock service. This call does not tell it to start, but it causes the service
// to call the onServiceConnected callback, which then starts FaceLock.
public void bindToFaceLock() {
if (usingFaceLock()) {
if (!mBoundToFaceLockService) {
if (DEBUG) Log.d(TAG, "before bind to FaceLock service");
mContext.bindService(new Intent(IFaceLockInterface.class.getName()),
mFaceLockConnection,
Context.BIND_AUTO_CREATE);
if (DEBUG) Log.d(TAG, "after bind to FaceLock service");
mBoundToFaceLockService = true;
} else {
Log.w(TAG, "Attempt to bind to FaceLock when already bound");
}
}
}
// Tells FaceLock to stop and then unbinds from the FaceLock service
public void stopAndUnbindFromFaceLock() {
if (usingFaceLock()) {
stopFaceLock();
if (mBoundToFaceLockService) {
if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
if (mFaceLockService != null) {
try {
mFaceLockService.unregisterCallback(mFaceLockCallback);
} catch (RemoteException e) {
// Not much we can do
}
}
mContext.unbindService(mFaceLockConnection);
if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
mBoundToFaceLockService = false;
} else {
// This is usually not an error when this happens. Sometimes we will tell it to
// unbind multiple times because it's called from both onWindowFocusChanged and
// onDetachedFromWindow.
if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
}
}
}
private ServiceConnection mFaceLockConnection = new ServiceConnection() {
// Completes connection, registers callback and starts FaceLock when service is bound
@Override
public void onServiceConnected(ComponentName className, IBinder iservice) {
mFaceLockService = IFaceLockInterface.Stub.asInterface(iservice);
if (DEBUG) Log.d(TAG, "Connected to FaceLock service");
try {
mFaceLockService.registerCallback(mFaceLockCallback);
} catch (RemoteException e) {
Log.e(TAG, "Caught exception connecting to FaceLock: " + e.toString());
mFaceLockService = null;
mBoundToFaceLockService = false;
return;
}
if (mFaceLockAreaView != null) {
int[] faceLockPosition;
faceLockPosition = new int[2];
mFaceLockAreaView.getLocationInWindow(faceLockPosition);
startFaceLock(mFaceLockAreaView.getWindowToken(),
faceLockPosition[0], faceLockPosition[1],
mFaceLockAreaView.getWidth(), mFaceLockAreaView.getHeight());
}
}
// Cleans up if FaceLock service unexpectedly disconnects
@Override
public void onServiceDisconnected(ComponentName className) {
synchronized(mFaceLockServiceRunningLock) {
mFaceLockService = null;
mFaceLockServiceRunning = false;
}
mBoundToFaceLockService = false;
Log.w(TAG, "Unexpected disconnect from FaceLock service");
}
};
// Tells the FaceLock service to start displaying its UI and perform recognition
public void startFaceLock(IBinder windowToken, int x, int y, int w, int h)
{
if (usingFaceLock()) {
synchronized (mFaceLockServiceRunningLock) {
if (!mFaceLockServiceRunning) {
if (DEBUG) Log.d(TAG, "Starting FaceLock");
try {
mFaceLockService.startUi(windowToken, x, y, w, h);
} catch (RemoteException e) {
Log.e(TAG, "Caught exception starting FaceLock: " + e.toString());
return;
}
mFaceLockServiceRunning = true;
} else {
if (DEBUG) Log.w(TAG, "startFaceLock() attempted while running");
}
}
}
}
// Tells the FaceLock service to stop displaying its UI and stop recognition
public void stopFaceLock()
{
if (usingFaceLock()) {
// Note that attempting to stop FaceLock when it's not running is not an issue.
// FaceLock can return, which stops it and then we try to stop it when the
// screen is turned off. That's why we check.
synchronized (mFaceLockServiceRunningLock) {
if (mFaceLockServiceRunning) {
try {
if (DEBUG) Log.d(TAG, "Stopping FaceLock");
mFaceLockService.stopUi();
} catch (RemoteException e) {
Log.e(TAG, "Caught exception stopping FaceLock: " + e.toString());
}
mFaceLockServiceRunning = false;
}
}
}
}
// Implements the FaceLock service callback interface defined in AIDL
private final IFaceLockCallback mFaceLockCallback = new IFaceLockCallback.Stub() {
// Stops the FaceLock UI and indicates that the phone should be unlocked
@Override
public void unlock() {
if (DEBUG) Log.d(TAG, "FaceLock unlock()");
showFaceLockArea(); // Keep fallback covered
stopAndUnbindFromFaceLock();
mKeyguardScreenCallback.keyguardDone(true);
mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
}
// Stops the FaceLock UI and exposes the backup method without unlocking
// This means the user has cancelled out
@Override
public void cancel() {
if (DEBUG) Log.d(TAG, "FaceLock cancel()");
hideFaceLockArea(); // Expose fallback
stopAndUnbindFromFaceLock();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
// Stops the FaceLock UI and exposes the backup method without unlocking
// This means FaceLock failed to recognize them
@Override
public void reportFailedAttempt() {
if (DEBUG) Log.d(TAG, "FaceLock reportFailedAttempt()");
mUpdateMonitor.reportFailedFaceUnlockAttempt();
hideFaceLockArea(); // Expose fallback
stopAndUnbindFromFaceLock();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
// Removes the black area that covers the backup unlock method
@Override
public void exposeFallback() {
if (DEBUG) Log.d(TAG, "FaceLock exposeFallback()");
hideFaceLockArea(); // Expose fallback
}
// Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
@Override
public void pokeWakelock() {
if (DEBUG) Log.d(TAG, "FaceLock pokeWakelock()");
mKeyguardScreenCallback.pokeWakelock();
}
};
}