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