| /* |
| * Copyright (C) 2021 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.systemui.statusbar.phone; |
| |
| import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; |
| import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; |
| import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ValueAnimator; |
| import android.content.res.Resources; |
| import android.hardware.biometrics.BiometricSourceType; |
| import android.util.MathUtils; |
| import android.view.View; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.keyguard.CarrierTextController; |
| import com.android.keyguard.KeyguardUpdateMonitor; |
| import com.android.keyguard.KeyguardUpdateMonitorCallback; |
| import com.android.systemui.R; |
| import com.android.systemui.animation.Interpolators; |
| import com.android.systemui.battery.BatteryMeterViewController; |
| import com.android.systemui.plugins.statusbar.StatusBarStateController; |
| import com.android.systemui.statusbar.StatusBarState; |
| import com.android.systemui.statusbar.SysuiStatusBarStateController; |
| import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; |
| import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; |
| import com.android.systemui.statusbar.notification.AnimatableProperty; |
| import com.android.systemui.statusbar.notification.PropertyAnimator; |
| import com.android.systemui.statusbar.notification.stack.AnimationProperties; |
| import com.android.systemui.statusbar.notification.stack.StackStateAnimator; |
| import com.android.systemui.statusbar.policy.BatteryController; |
| import com.android.systemui.statusbar.policy.ConfigurationController; |
| import com.android.systemui.statusbar.policy.KeyguardStateController; |
| import com.android.systemui.statusbar.policy.UserInfoController; |
| import com.android.systemui.util.ViewController; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| |
| /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */ |
| public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> { |
| private static final AnimationProperties KEYGUARD_HUN_PROPERTIES = |
| new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); |
| |
| private float mKeyguardHeadsUpShowingAmount = 0.0f; |
| private final AnimatableProperty mHeadsUpShowingAmountAnimation = AnimatableProperty.from( |
| "KEYGUARD_HEADS_UP_SHOWING_AMOUNT", |
| (view, aFloat) -> { |
| mKeyguardHeadsUpShowingAmount = aFloat; |
| updateViewState(); |
| }, |
| view -> mKeyguardHeadsUpShowingAmount, |
| R.id.keyguard_hun_animator_tag, |
| R.id.keyguard_hun_animator_end_tag, |
| R.id.keyguard_hun_animator_start_tag); |
| |
| private final CarrierTextController mCarrierTextController; |
| private final ConfigurationController mConfigurationController; |
| private final SystemStatusAnimationScheduler mAnimationScheduler; |
| private final BatteryController mBatteryController; |
| private final UserInfoController mUserInfoController; |
| private final StatusBarIconController mStatusBarIconController; |
| private final StatusBarIconController.TintedIconManager.Factory mTintedIconManagerFactory; |
| private final BatteryMeterViewController mBatteryMeterViewController; |
| private final NotificationPanelViewController.NotificationPanelViewStateProvider |
| mNotificationPanelViewStateProvider; |
| private final KeyguardStateController mKeyguardStateController; |
| private final KeyguardBypassController mKeyguardBypassController; |
| private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; |
| private final BiometricUnlockController mBiometricUnlockController; |
| private final SysuiStatusBarStateController mStatusBarStateController; |
| private final StatusBarContentInsetsProvider mInsetsProvider; |
| |
| private final ConfigurationController.ConfigurationListener mConfigurationListener = |
| new ConfigurationController.ConfigurationListener() { |
| @Override |
| public void onDensityOrFontScaleChanged() { |
| mView.loadDimens(); |
| } |
| |
| @Override |
| public void onThemeChanged() { |
| mView.onOverlayChanged(); |
| KeyguardStatusBarViewController.this.onThemeChanged(); |
| } |
| }; |
| |
| private final SystemStatusAnimationCallback mAnimationCallback = |
| new SystemStatusAnimationCallback() { |
| @Override |
| public void onSystemChromeAnimationStart() { |
| mView.onSystemChromeAnimationStart( |
| mAnimationScheduler.getAnimationState() == ANIMATING_OUT); |
| } |
| |
| @Override |
| public void onSystemChromeAnimationEnd() { |
| mView.onSystemChromeAnimationEnd( |
| mAnimationScheduler.getAnimationState() == ANIMATING_IN); |
| } |
| |
| @Override |
| public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator anim) { |
| mView.onSystemChromeAnimationUpdate((float) anim.getAnimatedValue()); |
| } |
| }; |
| |
| private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = |
| new BatteryController.BatteryStateChangeCallback() { |
| @Override |
| public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { |
| mView.onBatteryLevelChanged(charging); |
| } |
| }; |
| |
| private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = |
| (name, picture, userAccount) -> mView.onUserInfoChanged(picture); |
| |
| private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = |
| animation -> { |
| mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue(); |
| updateViewState(); |
| }; |
| |
| private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = |
| new KeyguardUpdateMonitorCallback() { |
| @Override |
| public void onBiometricAuthenticated( |
| int userId, |
| BiometricSourceType biometricSourceType, |
| boolean isStrongBiometric) { |
| if (mFirstBypassAttempt |
| && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( |
| isStrongBiometric)) { |
| mDelayShowingKeyguardStatusBar = true; |
| } |
| } |
| |
| @Override |
| public void onBiometricRunningStateChanged( |
| boolean running, |
| BiometricSourceType biometricSourceType) { |
| boolean keyguardOrShadeLocked = |
| mStatusBarState == KEYGUARD |
| || mStatusBarState == StatusBarState.SHADE_LOCKED; |
| if (!running |
| && mFirstBypassAttempt |
| && keyguardOrShadeLocked |
| && !mDozing |
| && !mDelayShowingKeyguardStatusBar |
| && !mBiometricUnlockController.isBiometricUnlock()) { |
| mFirstBypassAttempt = false; |
| animateKeyguardStatusBarIn(); |
| } |
| } |
| |
| @Override |
| public void onFinishedGoingToSleep(int why) { |
| mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); |
| mDelayShowingKeyguardStatusBar = false; |
| } |
| }; |
| |
| private final StatusBarStateController.StateListener mStatusBarStateListener = |
| new StatusBarStateController.StateListener() { |
| @Override |
| public void onStateChanged(int newState) { |
| mStatusBarState = newState; |
| } |
| }; |
| |
| private final List<String> mBlockedIcons; |
| private final int mNotificationsHeaderCollideDistance; |
| |
| private boolean mBatteryListening; |
| private StatusBarIconController.TintedIconManager mTintedIconManager; |
| |
| private float mKeyguardStatusBarAnimateAlpha = 1f; |
| /** |
| * If face auth with bypass is running for the first time after you turn on the screen. |
| * (From aod or screen off) |
| */ |
| private boolean mFirstBypassAttempt; |
| /** |
| * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until |
| * the keyguard is dismissed to show the status bar. |
| */ |
| private boolean mDelayShowingKeyguardStatusBar; |
| private int mStatusBarState; |
| private boolean mDozing; |
| private boolean mShowingKeyguardHeadsUp; |
| |
| @Inject |
| public 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 |
| ) { |
| super(view); |
| mCarrierTextController = carrierTextController; |
| mConfigurationController = configurationController; |
| mAnimationScheduler = animationScheduler; |
| mBatteryController = batteryController; |
| mUserInfoController = userInfoController; |
| mStatusBarIconController = statusBarIconController; |
| mTintedIconManagerFactory = tintedIconManagerFactory; |
| mBatteryMeterViewController = batteryMeterViewController; |
| mNotificationPanelViewStateProvider = notificationPanelViewStateProvider; |
| mKeyguardStateController = keyguardStateController; |
| mKeyguardBypassController = bypassController; |
| mKeyguardUpdateMonitor = keyguardUpdateMonitor; |
| mBiometricUnlockController = biometricUnlockController; |
| mStatusBarStateController = statusBarStateController; |
| mInsetsProvider = statusBarContentInsetsProvider; |
| |
| mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); |
| mKeyguardStateController.addCallback( |
| new KeyguardStateController.Callback() { |
| @Override |
| public void onKeyguardFadingAwayChanged() { |
| if (!mKeyguardStateController.isKeyguardFadingAway()) { |
| mFirstBypassAttempt = false; |
| mDelayShowingKeyguardStatusBar = false; |
| } |
| } |
| } |
| ); |
| |
| Resources r = getResources(); |
| mBlockedIcons = Collections.unmodifiableList(Arrays.asList( |
| r.getString(com.android.internal.R.string.status_bar_volume), |
| r.getString(com.android.internal.R.string.status_bar_alarm_clock), |
| r.getString(com.android.internal.R.string.status_bar_call_strength))); |
| mNotificationsHeaderCollideDistance = r.getDimensionPixelSize( |
| R.dimen.header_notifications_collide_distance); |
| } |
| |
| @Override |
| protected void onInit() { |
| super.onInit(); |
| mCarrierTextController.init(); |
| mBatteryMeterViewController.init(); |
| } |
| |
| @Override |
| protected void onViewAttached() { |
| mConfigurationController.addCallback(mConfigurationListener); |
| mAnimationScheduler.addCallback(mAnimationCallback); |
| mUserInfoController.addCallback(mOnUserInfoChangedListener); |
| mStatusBarStateController.addCallback(mStatusBarStateListener); |
| mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); |
| if (mTintedIconManager == null) { |
| mTintedIconManager = |
| mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons)); |
| mTintedIconManager.setBlockList(mBlockedIcons); |
| mStatusBarIconController.addIconGroup(mTintedIconManager); |
| } |
| mView.setOnApplyWindowInsetsListener( |
| (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); |
| |
| onThemeChanged(); |
| } |
| |
| @Override |
| protected void onViewDetached() { |
| mConfigurationController.removeCallback(mConfigurationListener); |
| mAnimationScheduler.removeCallback(mAnimationCallback); |
| mUserInfoController.removeCallback(mOnUserInfoChangedListener); |
| mStatusBarStateController.removeCallback(mStatusBarStateListener); |
| mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); |
| if (mTintedIconManager != null) { |
| mStatusBarIconController.removeIconGroup(mTintedIconManager); |
| } |
| } |
| |
| /** Should be called when the theme changes. */ |
| public void onThemeChanged() { |
| mView.onThemeChanged(mTintedIconManager); |
| } |
| |
| /** Sets whether user switcher is enabled. */ |
| public void setKeyguardUserSwitcherEnabled(boolean enabled) { |
| mView.setKeyguardUserSwitcherEnabled(enabled); |
| } |
| |
| /** Sets whether this controller should listen to battery updates. */ |
| public void setBatteryListening(boolean listening) { |
| if (listening == mBatteryListening) { |
| return; |
| } |
| mBatteryListening = listening; |
| if (mBatteryListening) { |
| mBatteryController.addCallback(mBatteryStateChangeCallback); |
| } else { |
| mBatteryController.removeCallback(mBatteryStateChangeCallback); |
| } |
| } |
| |
| /** Set the view to have no top clipping. */ |
| public void setNoTopClipping() { |
| mView.setTopClipping(0); |
| } |
| |
| /** |
| * Update the view's top clipping based on the value of notificationPanelTop and the view's |
| * current top. |
| * |
| * @param notificationPanelTop the current top of the notification panel view. |
| */ |
| public void updateTopClipping(int notificationPanelTop) { |
| mView.setTopClipping(notificationPanelTop - mView.getTop()); |
| } |
| |
| /** Sets the dozing state. */ |
| public void setDozing(boolean dozing) { |
| mDozing = dozing; |
| } |
| |
| /** Animate the keyguard status bar in. */ |
| public void animateKeyguardStatusBarIn() { |
| mView.setVisibility(View.VISIBLE); |
| mView.setAlpha(0f); |
| ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); |
| anim.addUpdateListener(mAnimatorUpdateListener); |
| anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); |
| anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); |
| anim.start(); |
| } |
| |
| /** Animate the keyguard status bar out. */ |
| public void animateKeyguardStatusBarOut(long startDelay, long duration) { |
| ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f); |
| anim.addUpdateListener(mAnimatorUpdateListener); |
| anim.setStartDelay(startDelay); |
| anim.setDuration(duration); |
| anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); |
| anim.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| mView.setVisibility(View.INVISIBLE); |
| mView.setAlpha(1f); |
| mKeyguardStatusBarAnimateAlpha = 1f; |
| } |
| }); |
| anim.start(); |
| } |
| |
| /** |
| * Updates the {@link KeyguardStatusBarView} state based on what the |
| * {@link NotificationPanelViewController.NotificationPanelViewStateProvider} and other |
| * controllers provide. |
| */ |
| public void updateViewState() { |
| if (!isKeyguardShowing()) { |
| return; |
| } |
| |
| float alphaQsExpansion = 1 - Math.min( |
| 1, mNotificationPanelViewStateProvider.getQsExpansionFraction() * 2); |
| float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion) |
| * mKeyguardStatusBarAnimateAlpha |
| * (1.0f - mKeyguardHeadsUpShowingAmount); |
| |
| boolean hideForBypass = |
| mFirstBypassAttempt && mKeyguardUpdateMonitor.shouldListenForFace() |
| || mDelayShowingKeyguardStatusBar; |
| int newVisibility = newAlpha != 0f && !mDozing && !hideForBypass |
| ? View.VISIBLE : View.INVISIBLE; |
| |
| updateViewState(newAlpha, newVisibility); |
| } |
| |
| /** |
| * Updates the {@link KeyguardStatusBarView} state based on the provided values. |
| */ |
| public void updateViewState(float alpha, int visibility) { |
| mView.setAlpha(alpha); |
| mView.setVisibility(visibility); |
| } |
| |
| /** |
| * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) |
| * during swiping up. |
| */ |
| private float getKeyguardContentsAlpha() { |
| float alpha; |
| if (isKeyguardShowing()) { |
| // When on Keyguard, we hide the header as soon as we expanded close enough to the |
| // header |
| alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight() |
| / (mView.getHeight() + mNotificationsHeaderCollideDistance); |
| } else { |
| // In SHADE_LOCKED, the top card is already really close to the header. Hide it as |
| // soon as we start translating the stack. |
| alpha = mNotificationPanelViewStateProvider.getPanelViewExpandedHeight() |
| / mView.getHeight(); |
| } |
| alpha = MathUtils.saturate(alpha); |
| alpha = (float) Math.pow(alpha, 0.75); |
| return alpha; |
| } |
| |
| /** |
| * Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and |
| * whether heads up is visible. |
| */ |
| public void updateForHeadsUp() { |
| updateForHeadsUp(true); |
| } |
| |
| void updateForHeadsUp(boolean animate) { |
| boolean showingKeyguardHeadsUp = |
| isKeyguardShowing() && mNotificationPanelViewStateProvider.shouldHeadsUpBeVisible(); |
| if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) { |
| mShowingKeyguardHeadsUp = showingKeyguardHeadsUp; |
| if (isKeyguardShowing()) { |
| PropertyAnimator.setProperty( |
| mView, |
| mHeadsUpShowingAmountAnimation, |
| showingKeyguardHeadsUp ? 1.0f : 0.0f, |
| KEYGUARD_HUN_PROPERTIES, |
| animate); |
| } else { |
| PropertyAnimator.applyImmediately(mView, mHeadsUpShowingAmountAnimation, 0.0f); |
| } |
| } |
| } |
| |
| private boolean isKeyguardShowing() { |
| return mStatusBarState == KEYGUARD; |
| } |
| |
| /** */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("KeyguardStatusBarView:"); |
| pw.println(" mBatteryListening: " + mBatteryListening); |
| mView.dump(fd, pw, args); |
| } |
| |
| } |