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