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