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