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