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