1 /* 2 * Copyright (C) 2021 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.StatusBarState.KEYGUARD; 20 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; 21 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ValueAnimator; 26 import android.content.res.Resources; 27 import android.hardware.biometrics.BiometricSourceType; 28 import android.util.MathUtils; 29 import android.view.View; 30 31 import androidx.annotation.NonNull; 32 33 import com.android.keyguard.CarrierTextController; 34 import com.android.keyguard.KeyguardUpdateMonitor; 35 import com.android.keyguard.KeyguardUpdateMonitorCallback; 36 import com.android.systemui.R; 37 import com.android.systemui.animation.Interpolators; 38 import com.android.systemui.battery.BatteryMeterViewController; 39 import com.android.systemui.plugins.statusbar.StatusBarStateController; 40 import com.android.systemui.statusbar.StatusBarState; 41 import com.android.systemui.statusbar.SysuiStatusBarStateController; 42 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; 43 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; 44 import com.android.systemui.statusbar.notification.AnimatableProperty; 45 import com.android.systemui.statusbar.notification.PropertyAnimator; 46 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 47 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 48 import com.android.systemui.statusbar.policy.BatteryController; 49 import com.android.systemui.statusbar.policy.ConfigurationController; 50 import com.android.systemui.statusbar.policy.KeyguardStateController; 51 import com.android.systemui.statusbar.policy.UserInfoController; 52 import com.android.systemui.util.ViewController; 53 54 import java.io.FileDescriptor; 55 import java.io.PrintWriter; 56 import java.util.Arrays; 57 import java.util.Collections; 58 import java.util.List; 59 60 import javax.inject.Inject; 61 62 /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */ 63 public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> { 64 private static final AnimationProperties KEYGUARD_HUN_PROPERTIES = 65 new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 66 67 private float mKeyguardHeadsUpShowingAmount = 0.0f; 68 private final AnimatableProperty mHeadsUpShowingAmountAnimation = AnimatableProperty.from( 69 "KEYGUARD_HEADS_UP_SHOWING_AMOUNT", 70 (view, aFloat) -> { 71 mKeyguardHeadsUpShowingAmount = aFloat; 72 updateViewState(); 73 }, 74 view -> mKeyguardHeadsUpShowingAmount, 75 R.id.keyguard_hun_animator_tag, 76 R.id.keyguard_hun_animator_end_tag, 77 R.id.keyguard_hun_animator_start_tag); 78 79 private final CarrierTextController mCarrierTextController; 80 private final ConfigurationController mConfigurationController; 81 private final SystemStatusAnimationScheduler mAnimationScheduler; 82 private final BatteryController mBatteryController; 83 private final UserInfoController mUserInfoController; 84 private final StatusBarIconController mStatusBarIconController; 85 private final StatusBarIconController.TintedIconManager.Factory mTintedIconManagerFactory; 86 private final BatteryMeterViewController mBatteryMeterViewController; 87 private final NotificationPanelViewController.NotificationPanelViewStateProvider 88 mNotificationPanelViewStateProvider; 89 private final KeyguardStateController mKeyguardStateController; 90 private final KeyguardBypassController mKeyguardBypassController; 91 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 92 private final BiometricUnlockController mBiometricUnlockController; 93 private final SysuiStatusBarStateController mStatusBarStateController; 94 private final StatusBarContentInsetsProvider mInsetsProvider; 95 96 private final ConfigurationController.ConfigurationListener mConfigurationListener = 97 new ConfigurationController.ConfigurationListener() { 98 @Override 99 public void onDensityOrFontScaleChanged() { 100 mView.loadDimens(); 101 } 102 103 @Override 104 public void onThemeChanged() { 105 mView.onOverlayChanged(); 106 KeyguardStatusBarViewController.this.onThemeChanged(); 107 } 108 }; 109 110 private final SystemStatusAnimationCallback mAnimationCallback = 111 new SystemStatusAnimationCallback() { 112 @Override 113 public void onSystemChromeAnimationStart() { 114 mView.onSystemChromeAnimationStart( 115 mAnimationScheduler.getAnimationState() == ANIMATING_OUT); 116 } 117 118 @Override 119 public void onSystemChromeAnimationEnd() { 120 mView.onSystemChromeAnimationEnd( 121 mAnimationScheduler.getAnimationState() == ANIMATING_IN); 122 } 123 124 @Override 125 public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator anim) { 126 mView.onSystemChromeAnimationUpdate((float) anim.getAnimatedValue()); 127 } 128 }; 129 130 private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = 131 new BatteryController.BatteryStateChangeCallback() { 132 @Override 133 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { 134 mView.onBatteryLevelChanged(charging); 135 } 136 }; 137 138 private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = 139 (name, picture, userAccount) -> mView.onUserInfoChanged(picture); 140 141 private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = 142 animation -> { 143 mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue(); 144 updateViewState(); 145 }; 146 147 private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = 148 new KeyguardUpdateMonitorCallback() { 149 @Override 150 public void onBiometricAuthenticated( 151 int userId, 152 BiometricSourceType biometricSourceType, 153 boolean isStrongBiometric) { 154 if (mFirstBypassAttempt 155 && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( 156 isStrongBiometric)) { 157 mDelayShowingKeyguardStatusBar = true; 158 } 159 } 160 161 @Override 162 public void onBiometricRunningStateChanged( 163 boolean running, 164 BiometricSourceType biometricSourceType) { 165 boolean keyguardOrShadeLocked = 166 mStatusBarState == KEYGUARD 167 || mStatusBarState == StatusBarState.SHADE_LOCKED; 168 if (!running 169 && mFirstBypassAttempt 170 && keyguardOrShadeLocked 171 && !mDozing 172 && !mDelayShowingKeyguardStatusBar 173 && !mBiometricUnlockController.isBiometricUnlock()) { 174 mFirstBypassAttempt = false; 175 animateKeyguardStatusBarIn(); 176 } 177 } 178 179 @Override 180 public void onFinishedGoingToSleep(int why) { 181 mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); 182 mDelayShowingKeyguardStatusBar = false; 183 } 184 }; 185 186 private final StatusBarStateController.StateListener mStatusBarStateListener = 187 new StatusBarStateController.StateListener() { 188 @Override 189 public void onStateChanged(int newState) { 190 mStatusBarState = newState; 191 } 192 }; 193 194 private final List<String> mBlockedIcons; 195 private final int mNotificationsHeaderCollideDistance; 196 197 private boolean mBatteryListening; 198 private StatusBarIconController.TintedIconManager mTintedIconManager; 199 200 private float mKeyguardStatusBarAnimateAlpha = 1f; 201 /** 202 * If face auth with bypass is running for the first time after you turn on the screen. 203 * (From aod or screen off) 204 */ 205 private boolean mFirstBypassAttempt; 206 /** 207 * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until 208 * the keyguard is dismissed to show the status bar. 209 */ 210 private boolean mDelayShowingKeyguardStatusBar; 211 private int mStatusBarState; 212 private boolean mDozing; 213 private boolean mShowingKeyguardHeadsUp; 214 215 @Inject KeyguardStatusBarViewController( KeyguardStatusBarView view, CarrierTextController carrierTextController, ConfigurationController configurationController, SystemStatusAnimationScheduler animationScheduler, BatteryController batteryController, UserInfoController userInfoController, StatusBarIconController statusBarIconController, StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory, BatteryMeterViewController batteryMeterViewController, NotificationPanelViewController.NotificationPanelViewStateProvider notificationPanelViewStateProvider, KeyguardStateController keyguardStateController, KeyguardBypassController bypassController, KeyguardUpdateMonitor keyguardUpdateMonitor, BiometricUnlockController biometricUnlockController, SysuiStatusBarStateController statusBarStateController, StatusBarContentInsetsProvider statusBarContentInsetsProvider )216 public KeyguardStatusBarViewController( 217 KeyguardStatusBarView view, 218 CarrierTextController carrierTextController, 219 ConfigurationController configurationController, 220 SystemStatusAnimationScheduler animationScheduler, 221 BatteryController batteryController, 222 UserInfoController userInfoController, 223 StatusBarIconController statusBarIconController, 224 StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory, 225 BatteryMeterViewController batteryMeterViewController, 226 NotificationPanelViewController.NotificationPanelViewStateProvider 227 notificationPanelViewStateProvider, 228 KeyguardStateController keyguardStateController, 229 KeyguardBypassController bypassController, 230 KeyguardUpdateMonitor keyguardUpdateMonitor, 231 BiometricUnlockController biometricUnlockController, 232 SysuiStatusBarStateController statusBarStateController, 233 StatusBarContentInsetsProvider statusBarContentInsetsProvider 234 ) { 235 super(view); 236 mCarrierTextController = carrierTextController; 237 mConfigurationController = configurationController; 238 mAnimationScheduler = animationScheduler; 239 mBatteryController = batteryController; 240 mUserInfoController = userInfoController; 241 mStatusBarIconController = statusBarIconController; 242 mTintedIconManagerFactory = tintedIconManagerFactory; 243 mBatteryMeterViewController = batteryMeterViewController; 244 mNotificationPanelViewStateProvider = notificationPanelViewStateProvider; 245 mKeyguardStateController = keyguardStateController; 246 mKeyguardBypassController = bypassController; 247 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 248 mBiometricUnlockController = biometricUnlockController; 249 mStatusBarStateController = statusBarStateController; 250 mInsetsProvider = statusBarContentInsetsProvider; 251 252 mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); 253 mKeyguardStateController.addCallback( 254 new KeyguardStateController.Callback() { 255 @Override 256 public void onKeyguardFadingAwayChanged() { 257 if (!mKeyguardStateController.isKeyguardFadingAway()) { 258 mFirstBypassAttempt = false; 259 mDelayShowingKeyguardStatusBar = false; 260 } 261 } 262 } 263 ); 264 265 Resources r = getResources(); 266 mBlockedIcons = Collections.unmodifiableList(Arrays.asList( 267 r.getString(com.android.internal.R.string.status_bar_volume), 268 r.getString(com.android.internal.R.string.status_bar_alarm_clock), 269 r.getString(com.android.internal.R.string.status_bar_call_strength))); 270 mNotificationsHeaderCollideDistance = r.getDimensionPixelSize( 271 R.dimen.header_notifications_collide_distance); 272 } 273 274 @Override onInit()275 protected void onInit() { 276 super.onInit(); 277 mCarrierTextController.init(); 278 mBatteryMeterViewController.init(); 279 } 280 281 @Override onViewAttached()282 protected void onViewAttached() { 283 mConfigurationController.addCallback(mConfigurationListener); 284 mAnimationScheduler.addCallback(mAnimationCallback); 285 mUserInfoController.addCallback(mOnUserInfoChangedListener); 286 mStatusBarStateController.addCallback(mStatusBarStateListener); 287 mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); 288 if (mTintedIconManager == null) { 289 mTintedIconManager = 290 mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons)); 291 mTintedIconManager.setBlockList(mBlockedIcons); 292 mStatusBarIconController.addIconGroup(mTintedIconManager); 293 } 294 mView.setOnApplyWindowInsetsListener( 295 (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); 296 297 onThemeChanged(); 298 } 299 300 @Override onViewDetached()301 protected void onViewDetached() { 302 mConfigurationController.removeCallback(mConfigurationListener); 303 mAnimationScheduler.removeCallback(mAnimationCallback); 304 mUserInfoController.removeCallback(mOnUserInfoChangedListener); 305 mStatusBarStateController.removeCallback(mStatusBarStateListener); 306 mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); 307 if (mTintedIconManager != null) { 308 mStatusBarIconController.removeIconGroup(mTintedIconManager); 309 } 310 } 311 312 /** Should be called when the theme changes. */ onThemeChanged()313 public void onThemeChanged() { 314 mView.onThemeChanged(mTintedIconManager); 315 } 316 317 /** Sets whether user switcher is enabled. */ setKeyguardUserSwitcherEnabled(boolean enabled)318 public void setKeyguardUserSwitcherEnabled(boolean enabled) { 319 mView.setKeyguardUserSwitcherEnabled(enabled); 320 } 321 322 /** Sets whether this controller should listen to battery updates. */ setBatteryListening(boolean listening)323 public void setBatteryListening(boolean listening) { 324 if (listening == mBatteryListening) { 325 return; 326 } 327 mBatteryListening = listening; 328 if (mBatteryListening) { 329 mBatteryController.addCallback(mBatteryStateChangeCallback); 330 } else { 331 mBatteryController.removeCallback(mBatteryStateChangeCallback); 332 } 333 } 334 335 /** Set the view to have no top clipping. */ setNoTopClipping()336 public void setNoTopClipping() { 337 mView.setTopClipping(0); 338 } 339 340 /** 341 * Update the view's top clipping based on the value of notificationPanelTop and the view's 342 * current top. 343 * 344 * @param notificationPanelTop the current top of the notification panel view. 345 */ updateTopClipping(int notificationPanelTop)346 public void updateTopClipping(int notificationPanelTop) { 347 mView.setTopClipping(notificationPanelTop - mView.getTop()); 348 } 349 350 /** Sets the dozing state. */ setDozing(boolean dozing)351 public void setDozing(boolean dozing) { 352 mDozing = dozing; 353 } 354 355 /** Animate the keyguard status bar in. */ animateKeyguardStatusBarIn()356 public void animateKeyguardStatusBarIn() { 357 mView.setVisibility(View.VISIBLE); 358 mView.setAlpha(0f); 359 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 360 anim.addUpdateListener(mAnimatorUpdateListener); 361 anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 362 anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 363 anim.start(); 364 } 365 366 /** Animate the keyguard status bar out. */ animateKeyguardStatusBarOut(long startDelay, long duration)367 public void animateKeyguardStatusBarOut(long startDelay, long duration) { 368 ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f); 369 anim.addUpdateListener(mAnimatorUpdateListener); 370 anim.setStartDelay(startDelay); 371 anim.setDuration(duration); 372 anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 373 anim.addListener(new AnimatorListenerAdapter() { 374 @Override 375 public void onAnimationEnd(Animator animation) { 376 mView.setVisibility(View.INVISIBLE); 377 mView.setAlpha(1f); 378 mKeyguardStatusBarAnimateAlpha = 1f; 379 } 380 }); 381 anim.start(); 382 } 383 384 /** 385 * Updates the {@link KeyguardStatusBarView} state based on what the 386 * {@link NotificationPanelViewController.NotificationPanelViewStateProvider} and other 387 * controllers provide. 388 */ updateViewState()389 public void updateViewState() { 390 if (!isKeyguardShowing()) { 391 return; 392 } 393 394 float alphaQsExpansion = 1 - Math.min( 395 1, mNotificationPanelViewStateProvider.getQsExpansionFraction() * 2); 396 float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion) 397 * mKeyguardStatusBarAnimateAlpha 398 * (1.0f - mKeyguardHeadsUpShowingAmount); 399 400 boolean hideForBypass = 401 mFirstBypassAttempt && mKeyguardUpdateMonitor.shouldListenForFace() 402 || mDelayShowingKeyguardStatusBar; 403 int newVisibility = newAlpha != 0f && !mDozing && !hideForBypass 404 ? View.VISIBLE : View.INVISIBLE; 405 406 updateViewState(newAlpha, newVisibility); 407 } 408 409 /** 410 * Updates the {@link KeyguardStatusBarView} state based on the provided values. 411 */ updateViewState(float alpha, int visibility)412 public void updateViewState(float alpha, int visibility) { 413 mView.setAlpha(alpha); 414 mView.setVisibility(visibility); 415 } 416 417 /** 418 * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) 419 * during swiping up. 420 */ getKeyguardContentsAlpha()421 private float getKeyguardContentsAlpha() { 422 float alpha; 423 if (isKeyguardShowing()) { 424 // When on Keyguard, we hide the header as soon as we expanded close enough to the 425 // header 426 alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight() 427 / (mView.getHeight() + mNotificationsHeaderCollideDistance); 428 } else { 429 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 430 // soon as we start translating the stack. 431 alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight() 432 / mView.getHeight(); 433 } 434 alpha = MathUtils.saturate(alpha); 435 alpha = (float) Math.pow(alpha, 0.75); 436 return alpha; 437 } 438 439 /** 440 * Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and 441 * whether heads up is visible. 442 */ updateForHeadsUp()443 public void updateForHeadsUp() { 444 updateForHeadsUp(true); 445 } 446 updateForHeadsUp(boolean animate)447 void updateForHeadsUp(boolean animate) { 448 boolean showingKeyguardHeadsUp = 449 isKeyguardShowing() && mNotificationPanelViewStateProvider.shouldHeadsUpBeVisible(); 450 if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) { 451 mShowingKeyguardHeadsUp = showingKeyguardHeadsUp; 452 if (isKeyguardShowing()) { 453 PropertyAnimator.setProperty( 454 mView, 455 mHeadsUpShowingAmountAnimation, 456 showingKeyguardHeadsUp ? 1.0f : 0.0f, 457 KEYGUARD_HUN_PROPERTIES, 458 animate); 459 } else { 460 PropertyAnimator.applyImmediately(mView, mHeadsUpShowingAmountAnimation, 0.0f); 461 } 462 } 463 } 464 isKeyguardShowing()465 private boolean isKeyguardShowing() { 466 return mStatusBarState == KEYGUARD; 467 } 468 469 /** */ dump(FileDescriptor fd, PrintWriter pw, String[] args)470 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 471 pw.println("KeyguardStatusBarView:"); 472 pw.println(" mBatteryListening: " + mBatteryListening); 473 mView.dump(fd, pw, args); 474 } 475 476 } 477