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 package com.android.systemui.statusbar.phone
17 
18 import android.view.View
19 import com.android.systemui.dagger.SysUISingleton
20 import com.android.systemui.keyguard.WakefulnessLifecycle
21 import com.android.systemui.shade.ShadeViewController
22 import com.android.systemui.statusbar.LightRevealScrim
23 import com.android.systemui.unfold.FoldAodAnimationController
24 import com.android.systemui.unfold.SysUIUnfoldComponent
25 import java.util.Optional
26 import javax.inject.Inject
27 
28 @SysUISingleton
29 class ScreenOffAnimationController @Inject constructor(
30     sysUiUnfoldComponent: Optional<SysUIUnfoldComponent>,
31     unlockedScreenOffAnimation: UnlockedScreenOffAnimationController,
32     private val wakefulnessLifecycle: WakefulnessLifecycle,
33 ) : WakefulnessLifecycle.Observer {
34 
35     private val foldToAodAnimation: FoldAodAnimationController? = sysUiUnfoldComponent
36         .orElse(null)?.getFoldAodAnimationController()
37 
38     private val animations: List<ScreenOffAnimation> =
39         listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
40 
initializenull41     fun initialize(
42             centralSurfaces: CentralSurfaces,
43             shadeViewController: ShadeViewController,
44             lightRevealScrim: LightRevealScrim,
45     ) {
46         animations.forEach { it.initialize(centralSurfaces, shadeViewController, lightRevealScrim) }
47         wakefulnessLifecycle.addObserver(this)
48     }
49 
50     /**
51      * Called when system reports that we are going to sleep
52      */
onStartedGoingToSleepnull53     override fun onStartedGoingToSleep() {
54         animations.firstOrNull { it.startAnimation() }
55     }
56 
57     /**
58      * Called when opaqueness of the light reveal scrim has change
59      * When [isOpaque] is true then scrim is visible and covers the screen
60      */
onScrimOpaqueChangednull61     fun onScrimOpaqueChanged(isOpaque: Boolean) =
62         animations.forEach { it.onScrimOpaqueChanged(isOpaque) }
63 
64     /**
65      * Called when always on display setting changed
66      */
onAlwaysOnChangednull67     fun onAlwaysOnChanged(alwaysOn: Boolean) =
68         animations.forEach { it.onAlwaysOnChanged(alwaysOn) }
69 
70     /**
71      * If returns true we are taking over the screen off animation from display manager to SysUI.
72      * We can play our custom animation instead of default fade out animation.
73      */
shouldControlUnlockedScreenOffnull74     fun shouldControlUnlockedScreenOff(): Boolean =
75         animations.any { it.shouldPlayAnimation() }
76 
77     /**
78      * If returns true it fully expands notification shade, it could be used to make
79      * the scrims visible
80      */
shouldExpandNotificationsnull81     fun shouldExpandNotifications(): Boolean =
82         animations.any { it.isAnimationPlaying() }
83 
84     /**
85      * If returns true it allows to perform custom animation for showing
86      * keyguard in [animateInKeyguard]
87      */
shouldAnimateInKeyguardnull88     fun shouldAnimateInKeyguard(): Boolean =
89         animations.any { it.shouldAnimateInKeyguard() }
90 
91     /**
92      * Called when keyguard is about to be displayed and allows to perform custom animation
93      */
animateInKeyguardnull94     fun animateInKeyguard(keyguardView: View, after: Runnable) =
95         animations.firstOrNull {
96             if (it.shouldAnimateInKeyguard()) {
97                 it.animateInKeyguard(keyguardView, after)
98                 true
99             } else {
100                 false
101             }
102         }
103 
104     /**
105      * If returns true it will disable propagating touches to apps and keyguard
106      */
shouldIgnoreKeyguardTouchesnull107     fun shouldIgnoreKeyguardTouches(): Boolean =
108         animations.any { it.isAnimationPlaying() }
109 
110     /**
111      * If returns true wake up won't be blocked when dozing
112      */
allowWakeUpIfDozingnull113     fun allowWakeUpIfDozing(): Boolean =
114         animations.all { !it.isAnimationPlaying() }
115 
116     /**
117      * Do not allow showing keyguard immediately so it could be postponed e.g. to the point when
118      * the animation ends
119      */
shouldDelayKeyguardShownull120     fun shouldDelayKeyguardShow(): Boolean =
121         animations.any { it.shouldDelayKeyguardShow() }
122 
123     /**
124      * Return true while we want to ignore requests to show keyguard, we need to handle pending
125      * keyguard lock requests manually
126      *
127      * @see [com.android.systemui.keyguard.KeyguardViewMediator.maybeHandlePendingLock]
128      */
isKeyguardShowDelayednull129     fun isKeyguardShowDelayed(): Boolean =
130         animations.any { it.isKeyguardShowDelayed() }
131 
132     /**
133      * Return true to ignore requests to hide keyguard
134      */
isKeyguardHideDelayednull135     fun isKeyguardHideDelayed(): Boolean =
136         animations.any { it.isKeyguardHideDelayed() }
137 
138     /**
139      * Return true to make the status bar expanded so we can animate [LightRevealScrim]
140      */
shouldShowLightRevealScrimnull141     fun shouldShowLightRevealScrim(): Boolean =
142         animations.any { it.shouldPlayAnimation() }
143 
144     /**
145      * Return true to indicate that we should hide [LightRevealScrim] when waking up
146      */
shouldHideLightRevealScrimOnWakeUpnull147     fun shouldHideLightRevealScrimOnWakeUp(): Boolean =
148         animations.any { it.shouldHideScrimOnWakeUp() }
149 
150     /**
151      * Return true to override the dozing state of the notifications to fully dozing,
152      * so the notifications are not visible when playing the animation
153      */
overrideNotificationsFullyDozingOnKeyguardnull154     fun overrideNotificationsFullyDozingOnKeyguard(): Boolean =
155         animations.any { it.overrideNotificationsDozeAmount() }
156 
157     /**
158      * Return true to hide the notifications footer ('manage'/'clear all' buttons)
159      */
shouldHideNotificationsFooternull160     fun shouldHideNotificationsFooter(): Boolean =
161         animations.any { it.isAnimationPlaying() }
162 
163     /**
164      * Return true to clamp screen brightness to 'dimmed' value when devices times out
165      * and goes to sleep
166      */
shouldClampDozeScreenBrightnessnull167     fun shouldClampDozeScreenBrightness(): Boolean =
168         animations.any { it.shouldPlayAnimation() }
169 
170     /**
171      * Return true to show AOD icons even when status bar is in 'shade' state (unlocked)
172      */
shouldShowAodIconsWhenShadenull173     fun shouldShowAodIconsWhenShade(): Boolean =
174         animations.any { it.shouldShowAodIconsWhenShade() }
175 
176     /**
177      * Return true to allow animation of appearing AOD icons
178      */
shouldAnimateAodIconsnull179     fun shouldAnimateAodIcons(): Boolean =
180         animations.all { it.shouldAnimateAodIcons() }
181 
182     /**
183      * Return true to animate doze state change, if returns false dozing will be applied without
184      * animation (sends only 0.0f or 1.0f dozing progress)
185      */
shouldAnimateDozingChangenull186     fun shouldAnimateDozingChange(): Boolean =
187         animations.all { it.shouldAnimateDozingChange() }
188 
189     /**
190      * Returns true when moving display state to power save mode should be
191      * delayed for a few seconds. This might be useful to play animations in full quality,
192      * without reducing FPS.
193      */
shouldDelayDisplayDozeTransitionnull194     fun shouldDelayDisplayDozeTransition(): Boolean =
195         animations.any { it.shouldDelayDisplayDozeTransition() }
196 
197     /**
198      * Return true to animate large <-> small clock transition
199      */
shouldAnimateClockChangenull200     fun shouldAnimateClockChange(): Boolean =
201         animations.all { it.shouldAnimateClockChange() }
202 }
203 
204 interface ScreenOffAnimation {
initializenull205     fun initialize(
206             centralSurfaces: CentralSurfaces,
207             shadeViewController: ShadeViewController,
208             lightRevealScrim: LightRevealScrim,
209     ) {}
210 
211     /**
212      * Called when started going to sleep, should return true if the animation will be played
213      */
startAnimationnull214     fun startAnimation(): Boolean = false
215 
216     fun shouldPlayAnimation(): Boolean = false
217     fun isAnimationPlaying(): Boolean = false
218 
219     fun onScrimOpaqueChanged(isOpaque: Boolean) {}
onAlwaysOnChangednull220     fun onAlwaysOnChanged(alwaysOn: Boolean) {}
221 
shouldAnimateInKeyguardnull222     fun shouldAnimateInKeyguard(): Boolean = false
223     fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
224 
225     fun shouldDelayKeyguardShow(): Boolean = false
226     fun isKeyguardShowDelayed(): Boolean = false
227     fun isKeyguardHideDelayed(): Boolean = false
228     fun shouldHideScrimOnWakeUp(): Boolean = false
229     fun overrideNotificationsDozeAmount(): Boolean = false
230     fun shouldShowAodIconsWhenShade(): Boolean = false
231     fun shouldDelayDisplayDozeTransition(): Boolean = false
232     fun shouldAnimateAodIcons(): Boolean = true
233     fun shouldAnimateDozingChange(): Boolean = true
234     fun shouldAnimateClockChange(): Boolean = true
235 }
236