| /* |
| * Copyright (C) 2012 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.keyguard; |
| |
| import android.content.Context; |
| import android.graphics.drawable.Drawable; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.telephony.TelephonyManager; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.IRotationWatcher; |
| import android.view.IWindowManager; |
| import android.view.View; |
| import android.widget.ImageButton; |
| import android.widget.LinearLayout; |
| |
| import com.android.internal.widget.LockPatternUtils; |
| |
| import java.lang.Math; |
| |
| public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView { |
| |
| private static final String TAG = "FULKeyguardFaceUnlockView"; |
| private static final boolean DEBUG = KeyguardConstants.DEBUG; |
| private KeyguardSecurityCallback mKeyguardSecurityCallback; |
| private LockPatternUtils mLockPatternUtils; |
| private BiometricSensorUnlock mBiometricUnlock; |
| private View mFaceUnlockAreaView; |
| private ImageButton mCancelButton; |
| private SecurityMessageDisplay mSecurityMessageDisplay; |
| private View mEcaView; |
| private Drawable mBouncerFrame; |
| |
| private boolean mIsBouncerVisibleToUser = false; |
| private final Object mIsBouncerVisibleToUserLock = new Object(); |
| |
| private int mLastRotation; |
| private boolean mWatchingRotation; |
| private final IWindowManager mWindowManager = |
| IWindowManager.Stub.asInterface(ServiceManager.getService("window")); |
| |
| private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() { |
| public void onRotationChanged(int rotation) { |
| if (DEBUG) Log.d(TAG, "onRotationChanged(): " + mLastRotation + "->" + rotation); |
| |
| // If the difference between the new rotation value and the previous rotation value is |
| // equal to 2, the rotation change was 180 degrees. This stops the biometric unlock |
| // and starts it in the new position. This is not performed for 90 degree rotations |
| // since a 90 degree rotation is a configuration change, which takes care of this for |
| // us. |
| if (Math.abs(rotation - mLastRotation) == 2) { |
| if (mBiometricUnlock != null) { |
| mBiometricUnlock.stop(); |
| maybeStartBiometricUnlock(); |
| } |
| } |
| mLastRotation = rotation; |
| } |
| }; |
| |
| public KeyguardFaceUnlockView(Context context) { |
| this(context, null); |
| } |
| |
| public KeyguardFaceUnlockView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| |
| initializeBiometricUnlockView(); |
| |
| mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this); |
| mEcaView = findViewById(R.id.keyguard_selector_fade_container); |
| View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame); |
| if (bouncerFrameView != null) { |
| mBouncerFrame = bouncerFrameView.getBackground(); |
| } |
| } |
| |
| @Override |
| public void setKeyguardCallback(KeyguardSecurityCallback callback) { |
| mKeyguardSecurityCallback = callback; |
| // TODO: formalize this in the interface or factor it out |
| ((FaceUnlock)mBiometricUnlock).setKeyguardCallback(callback); |
| } |
| |
| @Override |
| public void setLockPatternUtils(LockPatternUtils utils) { |
| mLockPatternUtils = utils; |
| } |
| |
| @Override |
| public void reset() { |
| |
| } |
| |
| @Override |
| public void onDetachedFromWindow() { |
| if (DEBUG) Log.d(TAG, "onDetachedFromWindow()"); |
| if (mBiometricUnlock != null) { |
| mBiometricUnlock.stop(); |
| } |
| KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback); |
| if (mWatchingRotation) { |
| try { |
| mWindowManager.removeRotationWatcher(mRotationWatcher); |
| mWatchingRotation = false; |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote exception when removing rotation watcher"); |
| } |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| if (DEBUG) Log.d(TAG, "onPause()"); |
| if (mBiometricUnlock != null) { |
| mBiometricUnlock.stop(); |
| } |
| KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback); |
| if (mWatchingRotation) { |
| try { |
| mWindowManager.removeRotationWatcher(mRotationWatcher); |
| mWatchingRotation = false; |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote exception when removing rotation watcher"); |
| } |
| } |
| } |
| |
| @Override |
| public void onResume(int reason) { |
| if (DEBUG) Log.d(TAG, "onResume()"); |
| synchronized (mIsBouncerVisibleToUserLock) { |
| mIsBouncerVisibleToUser = isBouncerVisibleToUser(); |
| } |
| KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback); |
| |
| // Registers a callback which handles stopping the biometric unlock and restarting it in |
| // the new position for a 180 degree rotation change. |
| if (!mWatchingRotation) { |
| try { |
| mLastRotation = mWindowManager.watchRotation(mRotationWatcher); |
| mWatchingRotation = true; |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote exception when adding rotation watcher"); |
| } |
| } |
| } |
| |
| @Override |
| public boolean needsInput() { |
| return false; |
| } |
| |
| @Override |
| public KeyguardSecurityCallback getCallback() { |
| return mKeyguardSecurityCallback; |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| super.onLayout(changed, l, t, r, b); |
| mBiometricUnlock.initializeView(mFaceUnlockAreaView); |
| } |
| |
| private void initializeBiometricUnlockView() { |
| if (DEBUG) Log.d(TAG, "initializeBiometricUnlockView()"); |
| mFaceUnlockAreaView = findViewById(R.id.face_unlock_area_view); |
| if (mFaceUnlockAreaView != null) { |
| mBiometricUnlock = new FaceUnlock(mContext); |
| |
| mCancelButton = (ImageButton) findViewById(R.id.face_unlock_cancel_button); |
| mCancelButton.setOnClickListener(new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mBiometricUnlock.stopAndShowBackup(); |
| } |
| }); |
| } else { |
| Log.w(TAG, "Couldn't find biometric unlock view"); |
| } |
| } |
| |
| /** |
| * Starts the biometric unlock if it should be started based on a number of factors. If it |
| * should not be started, it either goes to the back up, or remains showing to prepare for |
| * it being started later. |
| */ |
| private void maybeStartBiometricUnlock() { |
| if (DEBUG) Log.d(TAG, "maybeStartBiometricUnlock()"); |
| if (mBiometricUnlock != null) { |
| KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); |
| final boolean backupIsTimedOut = ( |
| monitor.getFailedUnlockAttempts() >= |
| LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT); |
| |
| boolean isBouncerVisibleToUser; |
| synchronized(mIsBouncerVisibleToUserLock) { |
| isBouncerVisibleToUser = mIsBouncerVisibleToUser; |
| } |
| |
| // Don't start it if the bouncer is not showing, but keep this view up because we want |
| // it here and ready for when the bouncer does show. |
| if (!isBouncerVisibleToUser) { |
| mBiometricUnlock.stop(); // It shouldn't be running but calling this can't hurt. |
| return; |
| } |
| |
| // Although these same conditions are handled in KeyguardSecurityModel, they are still |
| // necessary here. When a tablet is rotated 90 degrees, a configuration change is |
| // triggered and everything is torn down and reconstructed. That means |
| // KeyguardSecurityModel gets a chance to take care of the logic and doesn't even |
| // reconstruct KeyguardFaceUnlockView if the biometric unlock should be suppressed. |
| // However, for a 180 degree rotation, no configuration change is triggered, so only |
| // the logic here is capable of suppressing Face Unlock. |
| if (monitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE |
| && monitor.isAlternateUnlockEnabled() |
| && !monitor.getMaxBiometricUnlockAttemptsReached() |
| && !backupIsTimedOut) { |
| mBiometricUnlock.start(); |
| } else { |
| mBiometricUnlock.stopAndShowBackup(); |
| } |
| } |
| } |
| |
| // Returns true if the device is currently in a state where the user is seeing the bouncer. |
| // This requires isKeyguardBouncer() to be true, but that doesn't imply that the screen is on or |
| // the keyguard visibility is set to true, so we must check those conditions as well. |
| private boolean isBouncerVisibleToUser() { |
| KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); |
| return updateMonitor.isKeyguardBouncer() && updateMonitor.isKeyguardVisible() && |
| updateMonitor.isScreenOn(); |
| } |
| |
| // Starts the biometric unlock if the bouncer was not previously visible to the user, but is now |
| // visibile to the user. Stops the biometric unlock if the bouncer was previously visible to |
| // the user, but is no longer visible to the user. |
| private void handleBouncerUserVisibilityChanged() { |
| boolean wasBouncerVisibleToUser; |
| synchronized(mIsBouncerVisibleToUserLock) { |
| wasBouncerVisibleToUser = mIsBouncerVisibleToUser; |
| mIsBouncerVisibleToUser = isBouncerVisibleToUser(); |
| } |
| |
| if (mBiometricUnlock != null) { |
| if (wasBouncerVisibleToUser && !mIsBouncerVisibleToUser) { |
| mBiometricUnlock.stop(); |
| } else if (!wasBouncerVisibleToUser && mIsBouncerVisibleToUser) { |
| maybeStartBiometricUnlock(); |
| } |
| } |
| } |
| |
| KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { |
| // We need to stop the biometric unlock when a phone call comes in |
| @Override |
| public void onPhoneStateChanged(int phoneState) { |
| if (DEBUG) Log.d(TAG, "onPhoneStateChanged(" + phoneState + ")"); |
| if (phoneState == TelephonyManager.CALL_STATE_RINGING) { |
| if (mBiometricUnlock != null) { |
| mBiometricUnlock.stopAndShowBackup(); |
| } |
| } |
| } |
| |
| @Override |
| public void onUserSwitching(int userId) { |
| if (DEBUG) Log.d(TAG, "onUserSwitched(" + userId + ")"); |
| if (mBiometricUnlock != null) { |
| mBiometricUnlock.stop(); |
| } |
| // No longer required; static value set by KeyguardViewMediator |
| // mLockPatternUtils.setCurrentUser(userId); |
| } |
| |
| @Override |
| public void onUserSwitchComplete(int userId) { |
| if (DEBUG) Log.d(TAG, "onUserSwitchComplete(" + userId + ")"); |
| if (mBiometricUnlock != null) { |
| maybeStartBiometricUnlock(); |
| } |
| } |
| |
| @Override |
| public void onKeyguardVisibilityChanged(boolean showing) { |
| if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")"); |
| handleBouncerUserVisibilityChanged(); |
| } |
| |
| @Override |
| public void onKeyguardBouncerChanged(boolean bouncer) { |
| if (DEBUG) Log.d(TAG, "onKeyguardBouncerChanged(" + bouncer + ")"); |
| handleBouncerUserVisibilityChanged(); |
| } |
| |
| @Override |
| public void onScreenTurnedOn() { |
| if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); |
| handleBouncerUserVisibilityChanged(); |
| } |
| |
| @Override |
| public void onScreenTurnedOff(int why) { |
| if (DEBUG) Log.d(TAG, "onScreenTurnedOff()"); |
| handleBouncerUserVisibilityChanged(); |
| } |
| |
| @Override |
| public void onEmergencyCallAction() { |
| if (mBiometricUnlock != null) { |
| mBiometricUnlock.stop(); |
| } |
| } |
| }; |
| |
| @Override |
| public void showUsabilityHint() { |
| } |
| |
| @Override |
| public void showBouncer(int duration) { |
| KeyguardSecurityViewHelper. |
| showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); |
| } |
| |
| @Override |
| public void hideBouncer(int duration) { |
| KeyguardSecurityViewHelper. |
| hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); |
| } |
| |
| @Override |
| public void startAppearAnimation() { |
| // TODO. |
| } |
| |
| @Override |
| public boolean startDisappearAnimation(Runnable finishRunnable) { |
| return false; |
| } |
| } |