1 /* 2 * Copyright (C) 2019 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 17 package com.android.systemui.statusbar.phone; 18 19 import static com.android.systemui.statusbar.phone.LockIcon.STATE_BIOMETRICS_ERROR; 20 import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCKED; 21 import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCK_OPEN; 22 import static com.android.systemui.statusbar.phone.LockIcon.STATE_SCANNING_FACE; 23 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.graphics.Color; 28 import android.hardware.biometrics.BiometricSourceType; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.accessibility.AccessibilityNodeInfo; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.internal.logging.nano.MetricsProto; 36 import com.android.internal.widget.LockPatternUtils; 37 import com.android.keyguard.KeyguardUpdateMonitor; 38 import com.android.keyguard.KeyguardUpdateMonitorCallback; 39 import com.android.systemui.R; 40 import com.android.systemui.dagger.qualifiers.Main; 41 import com.android.systemui.dock.DockManager; 42 import com.android.systemui.plugins.statusbar.StatusBarStateController; 43 import com.android.systemui.statusbar.CommandQueue; 44 import com.android.systemui.statusbar.KeyguardIndicationController; 45 import com.android.systemui.statusbar.StatusBarState; 46 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; 47 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener; 48 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; 49 import com.android.systemui.statusbar.policy.AccessibilityController; 50 import com.android.systemui.statusbar.policy.ConfigurationController; 51 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; 52 import com.android.systemui.statusbar.policy.KeyguardStateController; 53 54 import java.util.Optional; 55 56 import javax.inject.Inject; 57 import javax.inject.Singleton; 58 59 /** Controls the {@link LockIcon} in the lockscreen. */ 60 @Singleton 61 public class LockscreenLockIconController { 62 63 private final LockscreenGestureLogger mLockscreenGestureLogger; 64 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 65 private final LockPatternUtils mLockPatternUtils; 66 private final ShadeController mShadeController; 67 private final AccessibilityController mAccessibilityController; 68 private final KeyguardIndicationController mKeyguardIndicationController; 69 private final StatusBarStateController mStatusBarStateController; 70 private final ConfigurationController mConfigurationController; 71 private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; 72 private final KeyguardBypassController mKeyguardBypassController; 73 private final Optional<DockManager> mDockManager; 74 private final KeyguardStateController mKeyguardStateController; 75 private final Resources mResources; 76 private final HeadsUpManagerPhone mHeadsUpManagerPhone; 77 private boolean mKeyguardShowing; 78 private boolean mKeyguardJustShown; 79 private boolean mBlockUpdates; 80 private boolean mSimLocked; 81 private boolean mTransientBiometricsError; 82 private boolean mDocked; 83 private boolean mWakeAndUnlockRunning; 84 private boolean mShowingLaunchAffordance; 85 private boolean mBouncerShowingScrimmed; 86 private boolean mFingerprintUnlock; 87 private int mStatusBarState = StatusBarState.SHADE; 88 private LockIcon mLockIcon; 89 90 private View.OnAttachStateChangeListener mOnAttachStateChangeListener = 91 new View.OnAttachStateChangeListener() { 92 @Override 93 public void onViewAttachedToWindow(View v) { 94 mStatusBarStateController.addCallback(mSBStateListener); 95 mConfigurationController.addCallback(mConfigurationListener); 96 mNotificationWakeUpCoordinator.addListener(mWakeUpListener); 97 mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); 98 mKeyguardStateController.addCallback(mKeyguardMonitorCallback); 99 100 mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener)); 101 102 mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure(); 103 mConfigurationListener.onThemeChanged(); 104 update(); 105 } 106 107 @Override 108 public void onViewDetachedFromWindow(View v) { 109 mStatusBarStateController.removeCallback(mSBStateListener); 110 mConfigurationController.removeCallback(mConfigurationListener); 111 mNotificationWakeUpCoordinator.removeListener(mWakeUpListener); 112 mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); 113 mKeyguardStateController.removeCallback(mKeyguardMonitorCallback); 114 115 mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener)); 116 } 117 }; 118 119 private final StatusBarStateController.StateListener mSBStateListener = 120 new StatusBarStateController.StateListener() { 121 @Override 122 public void onDozingChanged(boolean isDozing) { 123 setDozing(isDozing); 124 } 125 126 @Override 127 public void onPulsingChanged(boolean pulsing) { 128 setPulsing(pulsing); 129 } 130 131 @Override 132 public void onDozeAmountChanged(float linear, float eased) { 133 if (mLockIcon != null) { 134 mLockIcon.setDozeAmount(eased); 135 } 136 } 137 138 @Override 139 public void onStateChanged(int newState) { 140 setStatusBarState(newState); 141 } 142 }; 143 144 private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { 145 private int mDensity; 146 147 @Override 148 public void onThemeChanged() { 149 if (mLockIcon == null) { 150 return; 151 } 152 153 TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes( 154 null, new int[]{ R.attr.wallpaperTextColor }, 0, 0); 155 int iconColor = typedArray.getColor(0, Color.WHITE); 156 typedArray.recycle(); 157 mLockIcon.onThemeChange(iconColor); 158 } 159 160 @Override 161 public void onDensityOrFontScaleChanged() { 162 if (mLockIcon == null) { 163 return; 164 } 165 166 ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams(); 167 if (lp == null) { 168 return; 169 } 170 lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width); 171 lp.height = mLockIcon.getResources().getDimensionPixelSize( 172 R.dimen.keyguard_lock_height); 173 mLockIcon.setLayoutParams(lp); 174 update(true /* force */); 175 } 176 177 @Override 178 public void onLocaleListChanged() { 179 if (mLockIcon == null) { 180 return; 181 } 182 183 mLockIcon.setContentDescription( 184 mLockIcon.getResources().getText(R.string.accessibility_unlock_button)); 185 update(true /* force */); 186 } 187 188 @Override 189 public void onConfigChanged(Configuration newConfig) { 190 final int density = newConfig.densityDpi; 191 if (density != mDensity) { 192 mDensity = density; 193 update(); 194 } 195 } 196 }; 197 198 private final WakeUpListener mWakeUpListener = new WakeUpListener() { 199 @Override 200 public void onPulseExpansionChanged(boolean expandingChanged) { 201 } 202 203 @Override 204 public void onFullyHiddenChanged(boolean isFullyHidden) { 205 if (mKeyguardBypassController.getBypassEnabled()) { 206 boolean changed = updateIconVisibility(); 207 if (changed) { 208 update(); 209 } 210 } 211 } 212 }; 213 214 private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = 215 new KeyguardUpdateMonitorCallback() { 216 @Override 217 public void onSimStateChanged(int subId, int slotId, int simState) { 218 mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure(); 219 update(); 220 } 221 222 @Override 223 public void onKeyguardVisibilityChanged(boolean showing) { 224 update(); 225 } 226 227 @Override 228 public void onBiometricRunningStateChanged(boolean running, 229 BiometricSourceType biometricSourceType) { 230 update(); 231 } 232 233 @Override 234 public void onStrongAuthStateChanged(int userId) { 235 update(); 236 } 237 }; 238 239 private final DockManager.DockEventListener mDockEventListener = 240 event -> { 241 boolean docked = 242 event == DockManager.STATE_DOCKED || event == DockManager.STATE_DOCKED_HIDE; 243 if (docked != mDocked) { 244 mDocked = docked; 245 update(); 246 } 247 }; 248 249 private final KeyguardStateController.Callback mKeyguardMonitorCallback = 250 new KeyguardStateController.Callback() { 251 @Override 252 public void onKeyguardShowingChanged() { 253 boolean force = false; 254 boolean wasShowing = mKeyguardShowing; 255 mKeyguardShowing = mKeyguardStateController.isShowing(); 256 if (!wasShowing && mKeyguardShowing && mBlockUpdates) { 257 mBlockUpdates = false; 258 force = true; 259 } 260 if (!wasShowing && mKeyguardShowing) { 261 mKeyguardJustShown = true; 262 } 263 update(force); 264 } 265 266 @Override 267 public void onKeyguardFadingAwayChanged() { 268 if (!mKeyguardStateController.isKeyguardFadingAway()) { 269 if (mBlockUpdates) { 270 mBlockUpdates = false; 271 update(true /* force */); 272 } 273 } 274 } 275 276 @Override 277 public void onUnlockedChanged() { 278 update(); 279 } 280 }; 281 282 private final View.AccessibilityDelegate mAccessibilityDelegate = 283 new View.AccessibilityDelegate() { 284 @Override 285 public void onInitializeAccessibilityNodeInfo(View host, 286 AccessibilityNodeInfo info) { 287 super.onInitializeAccessibilityNodeInfo(host, info); 288 boolean fingerprintRunning = 289 mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); 290 // Only checking if unlocking with Biometric is allowed (no matter strong or 291 // non-strong as long as primary auth, i.e. PIN/pattern/password, is not 292 // required), so it's ok to pass true for isStrongBiometric to 293 // isUnlockingWithBiometricAllowed() to bypass the check of whether non-strong 294 // biometric is allowed 295 boolean unlockingAllowed = mKeyguardUpdateMonitor 296 .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */); 297 if (fingerprintRunning && unlockingAllowed) { 298 AccessibilityNodeInfo.AccessibilityAction unlock = 299 new AccessibilityNodeInfo.AccessibilityAction( 300 AccessibilityNodeInfo.ACTION_CLICK, 301 mResources.getString( 302 R.string.accessibility_unlock_without_fingerprint)); 303 info.addAction(unlock); 304 info.setHintText(mResources.getString( 305 R.string.accessibility_waiting_for_fingerprint)); 306 } else if (getState() == STATE_SCANNING_FACE) { 307 //Avoid 'button' to be spoken for scanning face 308 info.setClassName(LockIcon.class.getName()); 309 info.setContentDescription(mResources.getString( 310 R.string.accessibility_scanning_face)); 311 } 312 } 313 }; 314 private int mLastState; 315 316 @Inject LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, ShadeController shadeController, AccessibilityController accessibilityController, KeyguardIndicationController keyguardIndicationController, StatusBarStateController statusBarStateController, ConfigurationController configurationController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, KeyguardBypassController keyguardBypassController, @Nullable DockManager dockManager, KeyguardStateController keyguardStateController, @Main Resources resources, HeadsUpManagerPhone headsUpManagerPhone)317 public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger, 318 KeyguardUpdateMonitor keyguardUpdateMonitor, 319 LockPatternUtils lockPatternUtils, 320 ShadeController shadeController, 321 AccessibilityController accessibilityController, 322 KeyguardIndicationController keyguardIndicationController, 323 StatusBarStateController statusBarStateController, 324 ConfigurationController configurationController, 325 NotificationWakeUpCoordinator notificationWakeUpCoordinator, 326 KeyguardBypassController keyguardBypassController, 327 @Nullable DockManager dockManager, 328 KeyguardStateController keyguardStateController, 329 @Main Resources resources, 330 HeadsUpManagerPhone headsUpManagerPhone) { 331 mLockscreenGestureLogger = lockscreenGestureLogger; 332 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 333 mLockPatternUtils = lockPatternUtils; 334 mShadeController = shadeController; 335 mAccessibilityController = accessibilityController; 336 mKeyguardIndicationController = keyguardIndicationController; 337 mStatusBarStateController = statusBarStateController; 338 mConfigurationController = configurationController; 339 mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; 340 mKeyguardBypassController = keyguardBypassController; 341 mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager); 342 mKeyguardStateController = keyguardStateController; 343 mResources = resources; 344 mHeadsUpManagerPhone = headsUpManagerPhone; 345 346 mKeyguardIndicationController.setLockIconController(this); 347 } 348 349 /** 350 * Associate the controller with a {@link LockIcon} 351 * 352 * TODO: change to an init method and inject the view. 353 */ attach(LockIcon lockIcon)354 public void attach(LockIcon lockIcon) { 355 mLockIcon = lockIcon; 356 357 mLockIcon.setOnClickListener(this::handleClick); 358 mLockIcon.setOnLongClickListener(this::handleLongClick); 359 mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); 360 361 if (mLockIcon.isAttachedToWindow()) { 362 mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon); 363 } 364 mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener); 365 setStatusBarState(mStatusBarStateController.getState()); 366 } 367 getView()368 public LockIcon getView() { 369 return mLockIcon; 370 } 371 372 /** 373 * Called whenever the scrims become opaque, transparent or semi-transparent. 374 */ onScrimVisibilityChanged(Integer scrimsVisible)375 public void onScrimVisibilityChanged(Integer scrimsVisible) { 376 if (mWakeAndUnlockRunning 377 && scrimsVisible == ScrimController.TRANSPARENT) { 378 mWakeAndUnlockRunning = false; 379 update(); 380 } 381 } 382 383 /** 384 * Propagate {@link StatusBar} pulsing state. 385 */ setPulsing(boolean pulsing)386 private void setPulsing(boolean pulsing) { 387 update(); 388 } 389 390 /** 391 * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the 392 * icon on top of the black front scrim. 393 * We also want to halt padlock the animation when we're in face bypass mode or dismissing the 394 * keyguard with fingerprint. 395 * @param wakeAndUnlock are we wake and unlocking 396 * @param isUnlock are we currently unlocking 397 */ onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock, BiometricSourceType type)398 public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock, 399 BiometricSourceType type) { 400 if (wakeAndUnlock) { 401 mWakeAndUnlockRunning = true; 402 } 403 mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT; 404 if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled()) 405 && canBlockUpdates()) { 406 // We don't want the icon to change while we are unlocking 407 mBlockUpdates = true; 408 } 409 update(); 410 } 411 412 /** 413 * When we're launching an affordance, like double pressing power to open camera. 414 */ onShowingLaunchAffordanceChanged(Boolean showing)415 public void onShowingLaunchAffordanceChanged(Boolean showing) { 416 mShowingLaunchAffordance = showing; 417 update(); 418 } 419 420 /** Sets whether the bouncer is showing. */ setBouncerShowingScrimmed(boolean bouncerShowing)421 public void setBouncerShowingScrimmed(boolean bouncerShowing) { 422 mBouncerShowingScrimmed = bouncerShowing; 423 if (mKeyguardBypassController.getBypassEnabled()) { 424 update(); 425 } 426 } 427 428 /** 429 * Animate padlock opening when bouncer challenge is solved. 430 */ onBouncerPreHideAnimation()431 public void onBouncerPreHideAnimation() { 432 update(); 433 } 434 435 /** 436 * If we're currently presenting an authentication error message. 437 */ setTransientBiometricsError(boolean transientBiometricsError)438 public void setTransientBiometricsError(boolean transientBiometricsError) { 439 mTransientBiometricsError = transientBiometricsError; 440 update(); 441 } 442 handleLongClick(View view)443 private boolean handleLongClick(View view) { 444 mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK, 445 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); 446 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP); 447 mKeyguardIndicationController.showTransientIndication( 448 R.string.keyguard_indication_trust_disabled); 449 mKeyguardUpdateMonitor.onLockIconPressed(); 450 mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser()); 451 452 return true; 453 } 454 455 handleClick(View view)456 private void handleClick(View view) { 457 if (!mAccessibilityController.isAccessibilityEnabled()) { 458 return; 459 } 460 mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); 461 } 462 update()463 private void update() { 464 update(false /* force */); 465 } 466 update(boolean force)467 private void update(boolean force) { 468 int state = getState(); 469 boolean shouldUpdate = mLastState != state || force; 470 if (mBlockUpdates && canBlockUpdates()) { 471 shouldUpdate = false; 472 } 473 if (shouldUpdate && mLockIcon != null) { 474 mLockIcon.update(state, mStatusBarStateController.isPulsing(), 475 mStatusBarStateController.isDozing(), mKeyguardJustShown); 476 } 477 mLastState = state; 478 mKeyguardJustShown = false; 479 updateIconVisibility(); 480 updateClickability(); 481 } 482 getState()483 private int getState() { 484 if ((mKeyguardStateController.canDismissLockScreen() 485 || !mKeyguardStateController.isShowing() 486 || mKeyguardStateController.isKeyguardGoingAway() 487 || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) { 488 return STATE_LOCK_OPEN; 489 } else if (mTransientBiometricsError) { 490 return STATE_BIOMETRICS_ERROR; 491 } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning() 492 && !mStatusBarStateController.isPulsing()) { 493 return STATE_SCANNING_FACE; 494 } else { 495 return STATE_LOCKED; 496 } 497 } 498 canBlockUpdates()499 private boolean canBlockUpdates() { 500 return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway(); 501 } 502 setDozing(boolean isDozing)503 private void setDozing(boolean isDozing) { 504 update(); 505 } 506 507 /** Set the StatusBarState. */ setStatusBarState(int statusBarState)508 private void setStatusBarState(int statusBarState) { 509 mStatusBarState = statusBarState; 510 updateIconVisibility(); 511 } 512 513 /** 514 * Update the icon visibility 515 * @return true if the visibility changed 516 */ updateIconVisibility()517 private boolean updateIconVisibility() { 518 boolean onAodNotPulsingOrDocked = mStatusBarStateController.isDozing() 519 && (!mStatusBarStateController.isPulsing() || mDocked); 520 boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning 521 || mShowingLaunchAffordance; 522 boolean fingerprintOrBypass = mFingerprintUnlock 523 || mKeyguardBypassController.getBypassEnabled(); 524 if (fingerprintOrBypass && !mBouncerShowingScrimmed) { 525 if ((mHeadsUpManagerPhone.isHeadsUpGoingAway() 526 || mHeadsUpManagerPhone.hasPinnedHeadsUp() 527 || mStatusBarState == StatusBarState.KEYGUARD 528 || mStatusBarState == StatusBarState.SHADE) 529 && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) { 530 invisible = true; 531 } 532 } 533 534 if (mLockIcon == null) { 535 return false; 536 } 537 538 return mLockIcon.updateIconVisibility(!invisible); 539 } 540 updateClickability()541 private void updateClickability() { 542 if (mAccessibilityController == null) { 543 return; 544 } 545 boolean canLock = mKeyguardStateController.isMethodSecure() 546 && mKeyguardStateController.canDismissLockScreen(); 547 boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled(); 548 if (mLockIcon != null) { 549 mLockIcon.setClickable(clickToUnlock); 550 mLockIcon.setLongClickable(canLock && !clickToUnlock); 551 mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled()); 552 } 553 } 554 555 } 556