1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.keyguard; 17 18 import android.content.Context; 19 import android.graphics.drawable.Drawable; 20 import android.os.PowerManager; 21 import android.os.RemoteException; 22 import android.os.ServiceManager; 23 import android.telephony.TelephonyManager; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.IRotationWatcher; 27 import android.view.IWindowManager; 28 import android.view.View; 29 import android.widget.ImageButton; 30 import android.widget.LinearLayout; 31 32 import com.android.internal.widget.LockPatternUtils; 33 34 import java.lang.Math; 35 36 public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView { 37 38 private static final String TAG = "FULKeyguardFaceUnlockView"; 39 private static final boolean DEBUG = false; 40 private KeyguardSecurityCallback mKeyguardSecurityCallback; 41 private LockPatternUtils mLockPatternUtils; 42 private BiometricSensorUnlock mBiometricUnlock; 43 private View mFaceUnlockAreaView; 44 private ImageButton mCancelButton; 45 private SecurityMessageDisplay mSecurityMessageDisplay; 46 private View mEcaView; 47 private Drawable mBouncerFrame; 48 49 private boolean mIsShowing = false; 50 private final Object mIsShowingLock = new Object(); 51 52 private int mLastRotation; 53 private boolean mWatchingRotation; 54 private final IWindowManager mWindowManager = 55 IWindowManager.Stub.asInterface(ServiceManager.getService("window")); 56 57 private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() { 58 public void onRotationChanged(int rotation) { 59 if (DEBUG) Log.d(TAG, "onRotationChanged(): " + mLastRotation + "->" + rotation); 60 61 // If the difference between the new rotation value and the previous rotation value is 62 // equal to 2, the rotation change was 180 degrees. This stops the biometric unlock 63 // and starts it in the new position. This is not performed for 90 degree rotations 64 // since a 90 degree rotation is a configuration change, which takes care of this for 65 // us. 66 if (Math.abs(rotation - mLastRotation) == 2) { 67 if (mBiometricUnlock != null) { 68 mBiometricUnlock.stop(); 69 maybeStartBiometricUnlock(); 70 } 71 } 72 mLastRotation = rotation; 73 } 74 }; 75 76 public KeyguardFaceUnlockView(Context context) { 77 this(context, null); 78 } 79 80 public KeyguardFaceUnlockView(Context context, AttributeSet attrs) { 81 super(context, attrs); 82 } 83 84 @Override 85 protected void onFinishInflate() { 86 super.onFinishInflate(); 87 88 initializeBiometricUnlockView(); 89 90 mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this); 91 mEcaView = findViewById(R.id.keyguard_selector_fade_container); 92 View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame); 93 if (bouncerFrameView != null) { 94 mBouncerFrame = bouncerFrameView.getBackground(); 95 } 96 } 97 98 @Override 99 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 100 mKeyguardSecurityCallback = callback; 101 // TODO: formalize this in the interface or factor it out 102 ((FaceUnlock)mBiometricUnlock).setKeyguardCallback(callback); 103 } 104 105 @Override 106 public void setLockPatternUtils(LockPatternUtils utils) { 107 mLockPatternUtils = utils; 108 } 109 110 @Override 111 public void reset() { 112 113 } 114 115 @Override 116 public void onDetachedFromWindow() { 117 if (DEBUG) Log.d(TAG, "onDetachedFromWindow()"); 118 if (mBiometricUnlock != null) { 119 mBiometricUnlock.stop(); 120 } 121 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback); 122 if (mWatchingRotation) { 123 try { 124 mWindowManager.removeRotationWatcher(mRotationWatcher); 125 mWatchingRotation = false; 126 } catch (RemoteException e) { 127 Log.e(TAG, "Remote exception when removing rotation watcher"); 128 } 129 } 130 } 131 132 @Override 133 public void onPause() { 134 if (DEBUG) Log.d(TAG, "onPause()"); 135 if (mBiometricUnlock != null) { 136 mBiometricUnlock.stop(); 137 } 138 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateCallback); 139 if (mWatchingRotation) { 140 try { 141 mWindowManager.removeRotationWatcher(mRotationWatcher); 142 mWatchingRotation = false; 143 } catch (RemoteException e) { 144 Log.e(TAG, "Remote exception when removing rotation watcher"); 145 } 146 } 147 } 148 149 @Override 150 public void onResume(int reason) { 151 if (DEBUG) Log.d(TAG, "onResume()"); 152 mIsShowing = KeyguardUpdateMonitor.getInstance(mContext).isKeyguardVisible(); 153 if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { 154 maybeStartBiometricUnlock(); 155 } 156 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback); 157 158 // Registers a callback which handles stopping the biometric unlock and restarting it in 159 // the new position for a 180 degree rotation change. 160 if (!mWatchingRotation) { 161 try { 162 mLastRotation = mWindowManager.watchRotation(mRotationWatcher); 163 mWatchingRotation = true; 164 } catch (RemoteException e) { 165 Log.e(TAG, "Remote exception when adding rotation watcher"); 166 } 167 } 168 } 169 170 @Override 171 public boolean needsInput() { 172 return false; 173 } 174 175 @Override 176 public KeyguardSecurityCallback getCallback() { 177 return mKeyguardSecurityCallback; 178 } 179 180 @Override 181 protected void onLayout(boolean changed, int l, int t, int r, int b) { 182 super.onLayout(changed, l, t, r, b); 183 mBiometricUnlock.initializeView(mFaceUnlockAreaView); 184 } 185 186 private void initializeBiometricUnlockView() { 187 if (DEBUG) Log.d(TAG, "initializeBiometricUnlockView()"); 188 mFaceUnlockAreaView = findViewById(R.id.face_unlock_area_view); 189 if (mFaceUnlockAreaView != null) { 190 mBiometricUnlock = new FaceUnlock(mContext); 191 192 mCancelButton = (ImageButton) findViewById(R.id.face_unlock_cancel_button); 193 mCancelButton.setOnClickListener(new OnClickListener() { 194 @Override 195 public void onClick(View v) { 196 mBiometricUnlock.stopAndShowBackup(); 197 } 198 }); 199 } else { 200 Log.w(TAG, "Couldn't find biometric unlock view"); 201 } 202 } 203 204 /** 205 * Starts the biometric unlock if it should be started based on a number of factors. If it 206 * should not be started, it either goes to the back up, or remains showing to prepare for 207 * it being started later. 208 */ 209 private void maybeStartBiometricUnlock() { 210 if (DEBUG) Log.d(TAG, "maybeStartBiometricUnlock()"); 211 if (mBiometricUnlock != null) { 212 KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); 213 final boolean backupIsTimedOut = ( 214 monitor.getFailedUnlockAttempts() >= 215 LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT); 216 PowerManager powerManager = (PowerManager) mContext.getSystemService( 217 Context.POWER_SERVICE); 218 219 boolean isShowing; 220 synchronized(mIsShowingLock) { 221 isShowing = mIsShowing; 222 } 223 224 // Don't start it if the screen is off or if it's not showing, but keep this view up 225 // because we want it here and ready for when the screen turns on or when it does start 226 // showing. 227 if (!powerManager.isScreenOn() || !isShowing) { 228 mBiometricUnlock.stop(); // It shouldn't be running but calling this can't hurt. 229 return; 230 } 231 232 // Although these same conditions are handled in KeyguardSecurityModel, they are still 233 // necessary here. When a tablet is rotated 90 degrees, a configuration change is 234 // triggered and everything is torn down and reconstructed. That means 235 // KeyguardSecurityModel gets a chance to take care of the logic and doesn't even 236 // reconstruct KeyguardFaceUnlockView if the biometric unlock should be suppressed. 237 // However, for a 180 degree rotation, no configuration change is triggered, so only 238 // the logic here is capable of suppressing Face Unlock. 239 if (monitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE 240 && monitor.isAlternateUnlockEnabled() 241 && !monitor.getMaxBiometricUnlockAttemptsReached() 242 && !backupIsTimedOut) { 243 mBiometricUnlock.start(); 244 } else { 245 mBiometricUnlock.stopAndShowBackup(); 246 } 247 } 248 } 249 250 KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { 251 // We need to stop the biometric unlock when a phone call comes in 252 @Override 253 public void onPhoneStateChanged(int phoneState) { 254 if (DEBUG) Log.d(TAG, "onPhoneStateChanged(" + phoneState + ")"); 255 if (phoneState == TelephonyManager.CALL_STATE_RINGING) { 256 if (mBiometricUnlock != null) { 257 mBiometricUnlock.stopAndShowBackup(); 258 } 259 } 260 } 261 262 @Override 263 public void onUserSwitching(int userId) { 264 if (DEBUG) Log.d(TAG, "onUserSwitched(" + userId + ")"); 265 if (mBiometricUnlock != null) { 266 mBiometricUnlock.stop(); 267 } 268 // No longer required; static value set by KeyguardViewMediator 269 // mLockPatternUtils.setCurrentUser(userId); 270 } 271 272 @Override 273 public void onUserSwitchComplete(int userId) { 274 if (DEBUG) Log.d(TAG, "onUserSwitchComplete(" + userId + ")"); 275 if (mBiometricUnlock != null) { 276 maybeStartBiometricUnlock(); 277 } 278 } 279 280 @Override 281 public void onKeyguardVisibilityChanged(boolean showing) { 282 if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")"); 283 boolean wasShowing = false; 284 synchronized(mIsShowingLock) { 285 wasShowing = mIsShowing; 286 mIsShowing = showing; 287 } 288 PowerManager powerManager = (PowerManager) mContext.getSystemService( 289 Context.POWER_SERVICE); 290 if (mBiometricUnlock != null) { 291 if (!showing && wasShowing) { 292 mBiometricUnlock.stop(); 293 } else if (showing && powerManager.isScreenOn() && !wasShowing) { 294 maybeStartBiometricUnlock(); 295 } 296 } 297 } 298 299 @Override 300 public void onEmergencyCallAction() { 301 if (mBiometricUnlock != null) { 302 mBiometricUnlock.stop(); 303 } 304 } 305 }; 306 307 @Override 308 public void showUsabilityHint() { 309 } 310 311 @Override 312 public void showBouncer(int duration) { 313 KeyguardSecurityViewHelper. 314 showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); 315 } 316 317 @Override 318 public void hideBouncer(int duration) { 319 KeyguardSecurityViewHelper. 320 hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); 321 } 322 323 } 324