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