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