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