1 /* 2 * Copyright (C) 2023 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.wm.shell.keyguard; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 23 import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; 24 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; 25 import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; 26 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; 27 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; 28 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; 29 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; 30 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING; 31 import static android.view.WindowManager.TRANSIT_SLEEP; 32 import static android.view.WindowManager.TRANSIT_TO_BACK; 33 import static android.view.WindowManager.TRANSIT_TO_FRONT; 34 35 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; 36 37 import android.annotation.NonNull; 38 import android.annotation.Nullable; 39 import android.app.ActivityManager; 40 import android.os.Binder; 41 import android.os.Handler; 42 import android.os.IBinder; 43 import android.os.RemoteException; 44 import android.util.ArrayMap; 45 import android.util.Log; 46 import android.view.SurfaceControl; 47 import android.view.WindowManager; 48 import android.window.IRemoteTransition; 49 import android.window.IRemoteTransitionFinishedCallback; 50 import android.window.KeyguardState; 51 import android.window.TransitionInfo; 52 import android.window.TransitionRequestInfo; 53 import android.window.WindowContainerToken; 54 import android.window.WindowContainerTransaction; 55 56 import com.android.internal.protolog.ProtoLog; 57 import com.android.window.flags.Flags; 58 import com.android.wm.shell.common.DisplayController; 59 import com.android.wm.shell.common.ShellExecutor; 60 import com.android.wm.shell.common.TaskStackListenerCallback; 61 import com.android.wm.shell.common.TaskStackListenerImpl; 62 import com.android.wm.shell.protolog.ShellProtoLogGroup; 63 import com.android.wm.shell.shared.annotations.ExternalThread; 64 import com.android.wm.shell.sysui.KeyguardChangeListener; 65 import com.android.wm.shell.sysui.ShellController; 66 import com.android.wm.shell.sysui.ShellInit; 67 import com.android.wm.shell.transition.FocusTransitionObserver; 68 import com.android.wm.shell.transition.Transitions; 69 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; 70 71 /** 72 * The handler for Keyguard enter/exit and occlude/unocclude animations. 73 * 74 * <p>This takes the highest priority. 75 */ 76 public class KeyguardTransitionHandler 77 implements Transitions.TransitionHandler, KeyguardChangeListener, 78 TaskStackListenerCallback { 79 private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS = 80 Flags.ensureKeyguardDoesTransitionStarting(); 81 82 private static final String TAG = "KeyguardTransition"; 83 84 private final Transitions mTransitions; 85 private final ShellController mShellController; 86 87 private final DisplayController mDisplayController; 88 private final Handler mMainHandler; 89 private final ShellExecutor mMainExecutor; 90 91 private final ArrayMap<IBinder, StartedTransition> mStartedTransitions = new ArrayMap<>(); 92 private final TaskStackListenerImpl mTaskStackListener; 93 private final FocusTransitionObserver mFocusTransitionObserver; 94 95 /** 96 * Local IRemoteTransition implementations registered by the keyguard service. 97 * @see KeyguardTransitions 98 */ 99 private IRemoteTransition mExitTransition = null; 100 private IRemoteTransition mAppearTransition = null; 101 private IRemoteTransition mOccludeTransition = null; 102 private IRemoteTransition mOccludeByDreamTransition = null; 103 private IRemoteTransition mUnoccludeTransition = null; 104 105 // While set true, Keyguard has created a remote animation runner to handle the open app 106 // transition. 107 private boolean mIsLaunchingActivityOverLockscreen; 108 109 // Last value reported by {@link KeyguardChangeListener}. 110 private boolean mKeyguardShowing = true; 111 @Nullable 112 private WindowContainerToken mDreamToken; 113 114 private final class StartedTransition { 115 final TransitionInfo mInfo; 116 final SurfaceControl.Transaction mFinishT; 117 final IRemoteTransition mPlayer; 118 StartedTransition(TransitionInfo info, SurfaceControl.Transaction finishT, IRemoteTransition player)119 public StartedTransition(TransitionInfo info, 120 SurfaceControl.Transaction finishT, IRemoteTransition player) { 121 mInfo = info; 122 mFinishT = finishT; 123 mPlayer = player; 124 } 125 } 126 KeyguardTransitionHandler( @onNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull DisplayController displayController, @NonNull Transitions transitions, @NonNull TaskStackListenerImpl taskStackListener, @NonNull Handler mainHandler, @NonNull ShellExecutor mainExecutor, @NonNull FocusTransitionObserver focusTransitionObserver)127 public KeyguardTransitionHandler( 128 @NonNull ShellInit shellInit, 129 @NonNull ShellController shellController, 130 @NonNull DisplayController displayController, 131 @NonNull Transitions transitions, 132 @NonNull TaskStackListenerImpl taskStackListener, 133 @NonNull Handler mainHandler, 134 @NonNull ShellExecutor mainExecutor, 135 @NonNull FocusTransitionObserver focusTransitionObserver) { 136 mTransitions = transitions; 137 mShellController = shellController; 138 mDisplayController = displayController; 139 mMainHandler = mainHandler; 140 mMainExecutor = mainExecutor; 141 mTaskStackListener = taskStackListener; 142 shellInit.addInitCallback(this::onInit, this); 143 mFocusTransitionObserver = focusTransitionObserver; 144 } 145 onInit()146 private void onInit() { 147 mTransitions.addHandler(this); 148 mShellController.addKeyguardChangeListener(this); 149 if (dismissDreamOnKeyguardDismiss()) { 150 mTaskStackListener.addListener(this); 151 } 152 } 153 154 /** 155 * Interface for SystemUI implementations to set custom Keyguard exit/occlude handlers. 156 */ 157 @ExternalThread asKeyguardTransitions()158 public KeyguardTransitions asKeyguardTransitions() { 159 return new KeyguardTransitionsImpl(); 160 } 161 handles(TransitionInfo info)162 public static boolean handles(TransitionInfo info) { 163 // There is no animation for screen-wake unless we are immediately unlocking. 164 if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) { 165 return false; 166 } 167 return (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0; 168 } 169 170 @Override onKeyguardVisibilityChanged( boolean visible, boolean occluded, boolean animatingDismiss)171 public void onKeyguardVisibilityChanged( 172 boolean visible, boolean occluded, boolean animatingDismiss) { 173 mKeyguardShowing = visible; 174 } 175 isKeyguardShowing()176 public boolean isKeyguardShowing() { 177 return mKeyguardShowing; 178 } 179 isKeyguardAnimating()180 public boolean isKeyguardAnimating() { 181 return !mStartedTransitions.isEmpty(); 182 } 183 184 @Override onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)185 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { 186 mDreamToken = taskInfo.getActivityType() == ACTIVITY_TYPE_DREAM ? taskInfo.token : null; 187 } 188 189 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback)190 public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 191 @NonNull SurfaceControl.Transaction startTransaction, 192 @NonNull SurfaceControl.Transaction finishTransaction, 193 @NonNull TransitionFinishCallback finishCallback) { 194 if (!handles(info)) { 195 return false; 196 } 197 198 // Choose a transition applicable for the changes and keyguard state. 199 if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { 200 return startAnimation(mExitTransition, "going-away", 201 transition, info, startTransaction, finishTransaction, finishCallback); 202 } 203 204 if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 205 || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) { 206 return startAnimation(mAppearTransition, "appearing", 207 transition, info, startTransaction, finishTransaction, finishCallback); 208 } 209 210 if (mIsLaunchingActivityOverLockscreen) { 211 return false; 212 } 213 214 // Occlude/unocclude animations are only played if the keyguard is locked. 215 if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { 216 if (isKeyguardOccluding(info)) { 217 if (hasOpeningDream(info)) { 218 return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", 219 transition, info, startTransaction, finishTransaction, finishCallback); 220 } else { 221 return startAnimation(mOccludeTransition, "occlude", 222 transition, info, startTransaction, finishTransaction, finishCallback); 223 } 224 } else if (isKeyguardUnoccluding(info)) { 225 return startAnimation(mUnoccludeTransition, "unocclude", 226 transition, info, startTransaction, finishTransaction, finishCallback); 227 } 228 } 229 230 Log.i(TAG, "Refused to play keyguard transition: " + info); 231 return false; 232 } 233 startAnimation(IRemoteTransition remoteHandler, String description, @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback)234 private boolean startAnimation(IRemoteTransition remoteHandler, String description, 235 @NonNull IBinder transition, @NonNull TransitionInfo info, 236 @NonNull SurfaceControl.Transaction startTransaction, 237 @NonNull SurfaceControl.Transaction finishTransaction, 238 @NonNull TransitionFinishCallback finishCallback) { 239 240 if (remoteHandler == null) { 241 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 242 "missing handler for keyguard %s transition", description); 243 return false; 244 } 245 246 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 247 "start keyguard %s transition, info = %s", description, info); 248 try { 249 mStartedTransitions.put(transition, 250 new StartedTransition(info, finishTransaction, remoteHandler)); 251 remoteHandler.startAnimation(transition, info, startTransaction, 252 new IRemoteTransitionFinishedCallback.Stub() { 253 @Override 254 public void onTransitionFinished( 255 WindowContainerTransaction wct, SurfaceControl.Transaction sct) { 256 if (sct != null) { 257 finishTransaction.merge(sct); 258 } 259 final WindowContainerTransaction mergedWct = 260 new WindowContainerTransaction(); 261 if (wct != null) { 262 mergedWct.merge(wct, true); 263 } 264 maybeDismissFreeformOccludingKeyguard(mergedWct, info); 265 // Post our finish callback to let startAnimation finish first. 266 mMainExecutor.executeDelayed(() -> { 267 mStartedTransitions.remove(transition); 268 finishCallback.onTransitionFinished(mergedWct); 269 }, 0); 270 } 271 }); 272 } catch (RemoteException e) { 273 Log.wtf(TAG, "RemoteException thrown from local IRemoteTransition", e); 274 return false; 275 } 276 startTransaction.clear(); 277 return true; 278 } 279 280 @Override mergeAnimation(@onNull IBinder nextTransition, @NonNull TransitionInfo nextInfo, @NonNull SurfaceControl.Transaction nextT, @NonNull SurfaceControl.Transaction finishT, @NonNull IBinder currentTransition, @NonNull TransitionFinishCallback nextFinishCallback)281 public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo, 282 @NonNull SurfaceControl.Transaction nextT, @NonNull SurfaceControl.Transaction finishT, 283 @NonNull IBinder currentTransition, 284 @NonNull TransitionFinishCallback nextFinishCallback) { 285 final StartedTransition playing = mStartedTransitions.get(currentTransition); 286 if (playing == null) { 287 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 288 "unknown keyguard transition %s", currentTransition); 289 return; 290 } 291 if ((nextInfo.getFlags() & WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 292 && (playing.mInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { 293 // Keyguard unlocking has been canceled. Merge the unlock and re-lock transitions to 294 // avoid a flicker where we flash one frame with the screen fully unlocked. 295 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 296 "canceling keyguard exit transition %s", currentTransition); 297 playing.mFinishT.merge(nextT); 298 try { 299 playing.mPlayer.mergeAnimation(nextTransition, nextInfo, nextT, currentTransition, 300 new FakeFinishCallback()); 301 } catch (RemoteException e) { 302 // There is no good reason for this to happen because the player is a local object 303 // implementing an AIDL interface. 304 Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); 305 } 306 nextFinishCallback.onTransitionFinished(null); 307 } else { 308 // In all other cases, fast-forward to let the next queued transition start playing. 309 finishAnimationImmediately(currentTransition, playing); 310 } 311 } 312 313 @Override onTransitionConsumed(IBinder transition, boolean aborted, SurfaceControl.Transaction finishTransaction)314 public void onTransitionConsumed(IBinder transition, boolean aborted, 315 SurfaceControl.Transaction finishTransaction) { 316 final StartedTransition playing = mStartedTransitions.remove(transition); 317 if (playing != null) { 318 finishAnimationImmediately(transition, playing); 319 } 320 } 321 322 @Nullable 323 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)324 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 325 @NonNull TransitionRequestInfo request) { 326 if (dismissDreamOnKeyguardDismiss() 327 && (request.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0 328 && mDreamToken != null) { 329 // Dismiss the dream in the same transaction, so that it isn't visible once the device 330 // is unlocked. 331 return new WindowContainerTransaction().removeTask(mDreamToken); 332 } 333 return null; 334 } 335 hasOpeningDream(@onNull TransitionInfo info)336 private static boolean hasOpeningDream(@NonNull TransitionInfo info) { 337 for (int i = info.getChanges().size() - 1; i >= 0; i--) { 338 final TransitionInfo.Change change = info.getChanges().get(i); 339 if (isOpeningType(change.getMode()) 340 && change.getTaskInfo() != null 341 && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) { 342 return true; 343 } 344 } 345 return false; 346 } 347 isKeyguardOccluding(@onNull TransitionInfo info)348 private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) { 349 if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { 350 return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0; 351 } 352 353 for (int i = 0; i < info.getChanges().size(); i++) { 354 TransitionInfo.Change change = info.getChanges().get(i); 355 if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) 356 && change.getMode() == TRANSIT_TO_FRONT) { 357 return true; 358 } 359 } 360 return false; 361 } 362 isKeyguardUnoccluding(@onNull TransitionInfo info)363 private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) { 364 if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { 365 return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0; 366 } 367 368 for (int i = 0; i < info.getChanges().size(); i++) { 369 TransitionInfo.Change change = info.getChanges().get(i); 370 if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA) 371 && change.getMode() == TRANSIT_TO_BACK) { 372 return true; 373 } 374 } 375 return false; 376 } 377 finishAnimationImmediately(IBinder transition, StartedTransition playing)378 private void finishAnimationImmediately(IBinder transition, StartedTransition playing) { 379 final IBinder fakeTransition = new Binder(); 380 final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0); 381 final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction(); 382 final FakeFinishCallback fakeFinishCb = new FakeFinishCallback(); 383 try { 384 playing.mPlayer.mergeAnimation( 385 fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb); 386 } catch (RemoteException e) { 387 // There is no good reason for this to happen because the player is a local object 388 // implementing an AIDL interface. 389 Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e); 390 } 391 } 392 maybeDismissFreeformOccludingKeyguard( WindowContainerTransaction wct, TransitionInfo info)393 private void maybeDismissFreeformOccludingKeyguard( 394 WindowContainerTransaction wct, TransitionInfo info) { 395 if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) == 0) { 396 return; 397 } 398 // There's a window occluding the Keyguard, find it and if it's in freeform mode, change it 399 // to fullscreen. 400 for (int i = 0; i < info.getChanges().size(); i++) { 401 final TransitionInfo.Change change = info.getChanges().get(i); 402 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 403 if (taskInfo != null && taskInfo.taskId != INVALID_TASK_ID 404 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM 405 && mFocusTransitionObserver.hasGlobalFocus(taskInfo) 406 && change.getContainer() != null) { 407 wct.setWindowingMode(change.getContainer(), WINDOWING_MODE_FULLSCREEN); 408 wct.setBounds(change.getContainer(), null); 409 return; 410 } 411 } 412 } 413 414 private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub { 415 @Override onTransitionFinished( WindowContainerTransaction wct, SurfaceControl.Transaction t)416 public void onTransitionFinished( 417 WindowContainerTransaction wct, SurfaceControl.Transaction t) { 418 return; 419 } 420 } 421 422 @ExternalThread 423 private final class KeyguardTransitionsImpl implements KeyguardTransitions { 424 @Override register( IRemoteTransition exitTransition, IRemoteTransition appearTransition, IRemoteTransition occludeTransition, IRemoteTransition occludeByDreamTransition, IRemoteTransition unoccludeTransition)425 public void register( 426 IRemoteTransition exitTransition, 427 IRemoteTransition appearTransition, 428 IRemoteTransition occludeTransition, 429 IRemoteTransition occludeByDreamTransition, 430 IRemoteTransition unoccludeTransition) { 431 mMainExecutor.execute(() -> { 432 mExitTransition = exitTransition; 433 mAppearTransition = appearTransition; 434 mOccludeTransition = occludeTransition; 435 mOccludeByDreamTransition = occludeByDreamTransition; 436 mUnoccludeTransition = unoccludeTransition; 437 }); 438 } 439 440 @Override setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen)441 public void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) { 442 mMainExecutor.execute(() -> 443 mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen); 444 } 445 446 @Override startKeyguardTransition(boolean keyguardShowing, boolean aodShowing)447 public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) { 448 final WindowContainerTransaction wct = new WindowContainerTransaction(); 449 wct.addKeyguardState(new KeyguardState.Builder().setKeyguardShowing(keyguardShowing) 450 .setAodShowing(aodShowing).build()); 451 mMainExecutor.execute(() -> { 452 mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, 453 wct, KeyguardTransitionHandler.this); 454 }); 455 } 456 } 457 } 458