• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.keyguard
18 
19 import android.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.ValueAnimator
22 import android.content.Context
23 import android.graphics.Matrix
24 import android.os.RemoteException
25 import android.util.Log
26 import android.view.IRemoteAnimationFinishedCallback
27 import android.view.IRemoteAnimationRunner
28 import android.view.RemoteAnimationTarget
29 import android.view.SyncRtSurfaceTransactionApplier
30 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams
31 import android.view.View
32 import android.view.ViewGroup
33 import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
34 import androidx.annotation.VisibleForTesting
35 import com.android.app.animation.Interpolators
36 import com.android.internal.jank.InteractionJankMonitor
37 import com.android.internal.policy.ScreenDecorationsUtils
38 import com.android.keyguard.KeyguardViewController
39 import com.android.systemui.animation.ActivityTransitionAnimator
40 import com.android.systemui.animation.TransitionAnimator
41 import com.android.systemui.dagger.SysUISingleton
42 import com.android.systemui.dagger.qualifiers.Main
43 import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
44 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
45 import com.android.systemui.keyguard.shared.model.KeyguardState
46 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
47 import com.android.systemui.power.domain.interactor.PowerInteractor
48 import com.android.systemui.res.R
49 import com.android.systemui.shade.ShadeDisplayAware
50 import java.util.concurrent.Executor
51 import javax.inject.Inject
52 
53 private val UNOCCLUDE_ANIMATION_DURATION = 250
54 private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f
55 
56 /**
57  * Keeps track of Window Manager's occlusion state and RemoteAnimations related to changes in
58  * occlusion state. Occlusion is when a [FLAG_SHOW_WHEN_LOCKED] activity is displaying over the
59  * lockscreen - we're still locked, but the user can interact with the activity.
60  *
61  * Typical "occlusion" use cases include launching the camera over the lockscreen, tapping a quick
62  * affordance to bring up Google Pay/Wallet/whatever it's called by the time you're reading this,
63  * and Maps Navigation.
64  *
65  * Window Manager considers the keyguard to be 'occluded' whenever a [FLAG_SHOW_WHEN_LOCKED]
66  * activity is on top of the task stack, even if the device is unlocked and the keyguard is not
67  * visible. System UI considers the keyguard to be [KeyguardState.OCCLUDED] only when we're on the
68  * keyguard and an activity is displaying over it.
69  *
70  * For all System UI use cases, you should use [KeyguardTransitionInteractor] to determine if we're
71  * in the [KeyguardState.OCCLUDED] state and react accordingly. If you are sure that you need to
72  * check whether Window Manager considers OCCLUDED=true even though the lockscreen is not showing,
73  * use [KeyguardShowWhenLockedActivityInteractor.isShowWhenLockedActivityOnTop] in combination with
74  * [KeyguardTransitionInteractor] state.
75  *
76  * This is a very sensitive piece of state that has caused many headaches in the past. Please be
77  * careful.
78  */
79 @SysUISingleton
80 class WindowManagerOcclusionManager
81 @Inject
82 constructor(
83     val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
84     val activityTransitionAnimator: ActivityTransitionAnimator,
85     val keyguardViewController: dagger.Lazy<KeyguardViewController>,
86     val powerInteractor: PowerInteractor,
87     @ShadeDisplayAware val context: Context,
88     val interactionJankMonitor: InteractionJankMonitor,
89     @Main executor: Executor,
90     val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
91     val occlusionInteractor: KeyguardOcclusionInteractor,
92 ) {
93     val powerButtonY =
94         context.resources.getDimensionPixelSize(
95             R.dimen.physical_power_button_center_screen_location_y
96         )
97     val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
98 
99     var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
100 
101     /**
102      * Animation runner provided to WindowManager, which will be used if an occluding activity is
103      * launched and Window Manager wants us to animate it in. This is used as a signal that we are
104      * now occluded, and should update our state accordingly.
105      */
106     val occludeAnimationRunner: IRemoteAnimationRunner =
107         object : IRemoteAnimationRunner.Stub() {
108             override fun onAnimationStart(
109                 transit: Int,
110                 apps: Array<RemoteAnimationTarget>,
111                 wallpapers: Array<RemoteAnimationTarget>,
112                 nonApps: Array<RemoteAnimationTarget>,
113                 finishedCallback: IRemoteAnimationFinishedCallback?,
114             ) {
115                 Log.d(TAG, "occludeAnimationRunner#onAnimationStart")
116                 // Wrap the callback so that it's guaranteed to be nulled out once called.
117                 occludeAnimationFinishedCallback =
118                     object : IRemoteAnimationFinishedCallback.Stub() {
119                         override fun onAnimationFinished() {
120                             finishedCallback?.onAnimationFinished()
121                             occludeAnimationFinishedCallback = null
122                         }
123                     }
124                 keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
125                     showWhenLockedActivityOnTop = true,
126                     taskInfo = apps.firstOrNull()?.taskInfo,
127                 )
128                 activityTransitionAnimator
129                     .createEphemeralRunner(occludeAnimationController)
130                     .onAnimationStart(
131                         transit,
132                         apps,
133                         wallpapers,
134                         nonApps,
135                         occludeAnimationFinishedCallback,
136                     )
137             }
138 
139             override fun onAnimationCancelled() {
140                 Log.d(TAG, "occludeAnimationRunner#onAnimationCancelled")
141             }
142         }
143 
144     var unoccludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
145 
146     /**
147      * Animation runner provided to WindowManager, which will be used if an occluding activity is
148      * finished and Window Manager wants us to animate it out. This is used as a signal that we are
149      * no longer occluded, and should update our state accordingly.
150      *
151      * TODO(b/326464548): Restore dream specific animation.
152      */
153     val unoccludeAnimationRunner: IRemoteAnimationRunner =
154         object : IRemoteAnimationRunner.Stub() {
155             var unoccludeAnimator: ValueAnimator? = null
156             val unoccludeMatrix = Matrix()
157 
158             /** TODO(b/326470033): Extract this logic into ViewModels. */
159             override fun onAnimationStart(
160                 transit: Int,
161                 apps: Array<RemoteAnimationTarget>,
162                 wallpapers: Array<RemoteAnimationTarget>,
163                 nonApps: Array<RemoteAnimationTarget>,
164                 finishedCallback: IRemoteAnimationFinishedCallback?,
165             ) {
166                 Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart")
167                 // Wrap the callback so that it's guaranteed to be nulled out once called.
168                 unoccludeAnimationFinishedCallback =
169                     object : IRemoteAnimationFinishedCallback.Stub() {
170                         override fun onAnimationFinished() {
171                             finishedCallback?.onAnimationFinished()
172                             unoccludeAnimationFinishedCallback = null
173                         }
174                     }
175                 keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
176                     showWhenLockedActivityOnTop = false,
177                     taskInfo = apps.firstOrNull()?.taskInfo,
178                 )
179                 interactionJankMonitor.begin(
180                     createInteractionJankMonitorConf(
181                         InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION,
182                         "UNOCCLUDE",
183                     )
184                 )
185                 if (apps.isEmpty()) {
186                     Log.d(
187                         TAG,
188                         "No apps provided to unocclude runner; " +
189                             "skipping animation and unoccluding.",
190                     )
191                     unoccludeAnimationFinishedCallback?.onAnimationFinished()
192                     return
193                 }
194                 val target = apps[0]
195                 val localView: View = keyguardViewController.get().getViewRootImpl().getView()
196                 val applier = SyncRtSurfaceTransactionApplier(localView)
197                 // TODO(
198                 executor.execute {
199                     unoccludeAnimator?.cancel()
200                     unoccludeAnimator =
201                         ValueAnimator.ofFloat(1f, 0f).apply {
202                             duration = UNOCCLUDE_ANIMATION_DURATION.toLong()
203                             interpolator = Interpolators.TOUCH_RESPONSE
204                             addUpdateListener { animation: ValueAnimator ->
205                                 val animatedValue = animation.animatedValue as Float
206                                 val surfaceHeight: Float =
207                                     target.screenSpaceBounds.height().toFloat()
208 
209                                 unoccludeMatrix.setTranslate(
210                                     0f,
211                                     (1f - animatedValue) *
212                                         surfaceHeight *
213                                         UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT,
214                                 )
215 
216                                 SurfaceParams.Builder(target.leash)
217                                     .withAlpha(animatedValue)
218                                     .withMatrix(unoccludeMatrix)
219                                     .withCornerRadius(windowCornerRadius)
220                                     .build()
221                                     .also { applier.scheduleApply(it) }
222                             }
223                             addListener(
224                                 object : AnimatorListenerAdapter() {
225                                     override fun onAnimationEnd(animation: Animator) {
226                                         try {
227                                             unoccludeAnimationFinishedCallback
228                                                 ?.onAnimationFinished()
229                                             unoccludeAnimator = null
230                                             interactionJankMonitor.end(
231                                                 InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION
232                                             )
233                                         } catch (e: RemoteException) {
234                                             e.printStackTrace()
235                                         }
236                                     }
237                                 }
238                             )
239                             start()
240                         }
241                 }
242             }
243 
244             override fun onAnimationCancelled() {
245                 Log.d(TAG, "unoccludeAnimationRunner#onAnimationCancelled")
246                 context.mainExecutor.execute { unoccludeAnimator?.cancel() }
247                 Log.d(TAG, "Unocclude animation cancelled.")
248                 interactionJankMonitor.cancel(InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION)
249             }
250         }
251 
252     /**
253      * Called when Window Manager tells the KeyguardService directly that we're occluded or not
254      * occluded, without starting an occlude/unocclude remote animation. This happens if occlusion
255      * state changes without an animation (such as if a SHOW_WHEN_LOCKED activity is launched while
256      * we're unlocked), or if an animation has been cancelled/interrupted and Window Manager wants
257      * to make sure that we're in the correct state.
258      */
259     fun onKeyguardServiceSetOccluded(occluded: Boolean) {
260         Log.d(TAG, "#onKeyguardServiceSetOccluded($occluded)")
261         keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(occluded)
262     }
263 
264     @VisibleForTesting
265     val occludeAnimationController: ActivityTransitionAnimator.Controller =
266         object : ActivityTransitionAnimator.Controller {
267             override val isLaunching: Boolean = true
268 
269             override var transitionContainer: ViewGroup
270                 get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
271                 set(_) {
272                     // Should never be set.
273                 }
274 
275             /** TODO(b/326470033): Extract this logic into ViewModels. */
276             override fun createAnimatorState(): TransitionAnimator.State {
277                 val fullWidth = transitionContainer.width
278                 val fullHeight = transitionContainer.height
279 
280                 if (
281                     keyguardOcclusionInteractor.showWhenLockedActivityLaunchedFromPowerGesture.value
282                 ) {
283                     val initialHeight = fullHeight / 3f
284                     val initialWidth = fullWidth / 3f
285 
286                     // Start the animation near the power button, at one-third size, since the
287                     // camera was launched from the power button.
288                     return TransitionAnimator.State(
289                         top = (powerButtonY - initialHeight / 2f).toInt(),
290                         bottom = (powerButtonY + initialHeight / 2f).toInt(),
291                         left = (fullWidth - initialWidth).toInt(),
292                         right = fullWidth,
293                         topCornerRadius = windowCornerRadius,
294                         bottomCornerRadius = windowCornerRadius,
295                     )
296                 } else {
297                     val initialHeight = fullHeight / 2f
298                     val initialWidth = fullWidth / 2f
299 
300                     // Start the animation in the center of the screen, scaled down to half
301                     // size.
302                     return TransitionAnimator.State(
303                         top = (fullHeight - initialHeight).toInt() / 2,
304                         bottom = (initialHeight + (fullHeight - initialHeight) / 2).toInt(),
305                         left = (fullWidth - initialWidth).toInt() / 2,
306                         right = (initialWidth + (fullWidth - initialWidth) / 2).toInt(),
307                         topCornerRadius = windowCornerRadius,
308                         bottomCornerRadius = windowCornerRadius,
309                     )
310                 }
311             }
312         }
313 
314     private fun createInteractionJankMonitorConf(
315         cuj: Int,
316         tag: String?,
317     ): InteractionJankMonitor.Configuration.Builder {
318         val builder =
319             InteractionJankMonitor.Configuration.Builder.withView(
320                 cuj,
321                 keyguardViewController.get().getViewRootImpl().view,
322             )
323         return if (tag != null) builder.setTag(tag) else builder
324     }
325 
326     companion object {
327         val TAG = "WindowManagerOcclusion"
328     }
329 }
330