| /* |
| * 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(); |
| } |
| }; |
| } |