• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.systemui.animation;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_OLD_NONE;
22 import static android.view.WindowManager.TRANSIT_OPEN;
23 import static android.view.WindowManager.TRANSIT_TO_BACK;
24 import static android.view.WindowManager.TRANSIT_TO_FRONT;
25 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX;
26 import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX;
27 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
28 
29 import static com.android.internal.util.Preconditions.checkArgument;
30 import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY;
31 import static com.android.wm.shell.shared.TransitionUtil.isClosingMode;
32 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
33 import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
34 
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.util.ArrayMap;
38 import android.util.Log;
39 import android.view.IRemoteAnimationFinishedCallback;
40 import android.view.IRemoteAnimationRunner;
41 import android.view.RemoteAnimationTarget;
42 import android.view.SurfaceControl;
43 import android.view.WindowManager;
44 import android.view.WindowManager.TransitionOldType;
45 import android.window.IRemoteTransition;
46 import android.window.IRemoteTransitionFinishedCallback;
47 import android.window.RemoteTransitionStub;
48 import android.window.TransitionInfo;
49 
50 import com.android.wm.shell.shared.CounterRotator;
51 
52 public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
53     private static final String TAG = "RemoteAnimRunnerCompat";
54 
onAnimationStart(@indowManager.TransitionOldType int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback)55     public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
56             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
57             RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
58 
59     @Override
onAnimationStart(@ransitionOldType int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, final IRemoteAnimationFinishedCallback finishedCallback)60     public final void onAnimationStart(@TransitionOldType int transit,
61             RemoteAnimationTarget[] apps,
62             RemoteAnimationTarget[] wallpapers,
63             RemoteAnimationTarget[] nonApps,
64             final IRemoteAnimationFinishedCallback finishedCallback) {
65 
66         onAnimationStart(transit, apps, wallpapers,
67                 nonApps, () -> {
68                     try {
69                         finishedCallback.onAnimationFinished();
70                     } catch (RemoteException e) {
71                         Log.e(TAG, "Failed to call app controlled animation finished callback", e);
72                     }
73                 });
74     }
75 
toRemoteTransition()76     public IRemoteTransition toRemoteTransition() {
77         return wrap(this);
78     }
79 
80     /** Wraps a remote animation runner in a remote-transition. */
wrap(IRemoteAnimationRunner runner)81     public static RemoteTransitionStub wrap(IRemoteAnimationRunner runner) {
82         return new RemoteTransitionStub() {
83             final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
84 
85             @Override
86             public void startAnimation(IBinder token, TransitionInfo info,
87                     SurfaceControl.Transaction t,
88                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
89                 final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
90                 final RemoteAnimationTarget[] apps =
91                         RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
92                 final RemoteAnimationTarget[] wallpapers =
93                         RemoteAnimationTargetCompat.wrapNonApps(
94                                 info, true /* wallpapers */, t, leashMap);
95                 final RemoteAnimationTarget[] nonApps =
96                         RemoteAnimationTargetCompat.wrapNonApps(
97                                 info, false /* wallpapers */, t, leashMap);
98 
99                 // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
100                 boolean isReturnToHome = false;
101                 TransitionInfo.Change launcherTask = null;
102                 TransitionInfo.Change wallpaper = null;
103                 int launcherLayer = 0;
104                 int rotateDelta = 0;
105                 float displayW = 0;
106                 float displayH = 0;
107                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
108                     final TransitionInfo.Change change = info.getChanges().get(i);
109                     // skip changes that we didn't wrap
110                     if (!leashMap.containsKey(change.getLeash())) continue;
111                     if (change.getTaskInfo() != null
112                             && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
113                         isReturnToHome = change.getMode() == TRANSIT_OPEN
114                                 || change.getMode() == TRANSIT_TO_FRONT;
115                         launcherTask = change;
116                         launcherLayer = info.getChanges().size() - i;
117                     } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
118                         wallpaper = change;
119                     }
120                     if (change.getParent() == null && change.getEndRotation() >= 0
121                             && change.getEndRotation() != change.getStartRotation()) {
122                         rotateDelta = change.getEndRotation() - change.getStartRotation();
123                         displayW = change.getEndAbsBounds().width();
124                         displayH = change.getEndAbsBounds().height();
125                     }
126                 }
127 
128                 // Prepare for rotation if there is one
129                 final CounterRotator counterLauncher = new CounterRotator();
130                 final CounterRotator counterWallpaper = new CounterRotator();
131                 if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
132                     final TransitionInfo.Change parent = info.getChange(launcherTask.getParent());
133                     if (parent != null) {
134                         counterLauncher.setup(t, parent.getLeash(), rotateDelta, displayW,
135                                 displayH);
136                     } else {
137                         Log.e(TAG, "Malformed: " + launcherTask + " has parent="
138                                 + launcherTask.getParent() + " but it's not in info.");
139                     }
140                     if (counterLauncher.getSurface() != null) {
141                         t.setLayer(counterLauncher.getSurface(), launcherLayer);
142                     }
143                 }
144 
145                 if (isReturnToHome) {
146                     if (counterLauncher.getSurface() != null) {
147                         t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
148                     }
149                     // Need to "boost" the closing things since that's what launcher expects.
150                     for (int i = info.getChanges().size() - 1; i >= 0; --i) {
151                         final TransitionInfo.Change change = info.getChanges().get(i);
152                         final SurfaceControl leash = leashMap.get(change.getLeash());
153                         // skip changes that we didn't wrap
154                         if (leash == null) continue;
155                         final int mode = info.getChanges().get(i).getMode();
156                         // Only deal with independent layers
157                         if (!TransitionInfo.isIndependent(change, info)) continue;
158                         if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
159                             t.setLayer(leash, info.getChanges().size() * 3 - i);
160                             counterLauncher.addChild(t, leash);
161                         }
162                     }
163                     // Make wallpaper visible immediately since launcher apparently won't do this.
164                     for (int i = wallpapers.length - 1; i >= 0; --i) {
165                         t.show(wallpapers[i].leash);
166                         t.setAlpha(wallpapers[i].leash, 1.f);
167                     }
168                     if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()) {
169                         resetLauncherAlphaOnDesktopExit(info, launcherTask, leashMap, t);
170                     }
171                 } else {
172                     if (launcherTask != null) {
173                         counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
174                     }
175                     if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
176                         final TransitionInfo.Change parent = info.getChange(wallpaper.getParent());
177                         if (parent != null) {
178                             counterWallpaper.setup(t, parent.getLeash(), rotateDelta, displayW,
179                                     displayH);
180                         } else {
181                             Log.e(TAG, "Malformed: " + wallpaper + " has parent="
182                                     + wallpaper.getParent() + " but it's not in info.");
183                         }
184                         if (counterWallpaper.getSurface() != null) {
185                             t.setLayer(counterWallpaper.getSurface(), -1);
186                             counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
187                         }
188                     }
189                 }
190                 t.apply();
191 
192                 final Runnable animationFinishedCallback = () -> {
193                     final SurfaceControl.Transaction finishTransaction =
194                             new SurfaceControl.Transaction();
195                     counterLauncher.cleanUp(finishTransaction);
196                     counterWallpaper.cleanUp(finishTransaction);
197                     // Release surface references now. This is apparently to free GPU memory
198                     // before GC would.
199                     info.releaseAllSurfaces();
200                     // Don't release here since launcher might still be using them. Instead
201                     // let launcher release them (eg. via RemoteAnimationTargets)
202                     leashMap.clear();
203                     try {
204                         finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
205                         finishTransaction.close();
206                     } catch (RemoteException e) {
207                         Log.e(TAG, "Failed to call app controlled animation finished callback", e);
208                     }
209                 };
210                 synchronized (mFinishRunnables) {
211                     mFinishRunnables.put(token, animationFinishedCallback);
212                 }
213                 // TODO(bc-unlcok): Pass correct transit type.
214                 runner.onAnimationStart(TRANSIT_OLD_NONE,
215                         apps, wallpapers, nonApps, new IRemoteAnimationFinishedCallback() {
216                             @Override
217                             public void onAnimationFinished() {
218                                 synchronized (mFinishRunnables) {
219                                     if (mFinishRunnables.remove(token) == null) return;
220                                 }
221                                 animationFinishedCallback.run();
222                             }
223 
224                             @Override
225                             public IBinder asBinder() {
226                                 return null;
227                             }
228                         });
229             }
230 
231             @Override
232             public void mergeAnimation(IBinder token, TransitionInfo info,
233                     SurfaceControl.Transaction t, IBinder mergeTarget,
234                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
235                 // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
236                 //       to legacy cancel.
237                 final Runnable finishRunnable;
238                 synchronized (mFinishRunnables) {
239                     finishRunnable = mFinishRunnables.remove(mergeTarget);
240                 }
241                 // Since we're not actually animating, release native memory now
242                 t.close();
243                 info.releaseAllSurfaces();
244                 if (finishRunnable == null) return;
245                 runner.onAnimationCancelled();
246                 finishRunnable.run();
247             }
248 
249             @Override
250             public void onTransitionConsumed(IBinder transition, boolean aborted)
251                     throws RemoteException {
252                 // Notify the remote runner that the transition has been canceled if the transition
253                 // was merged into another transition or aborted
254                 synchronized (mFinishRunnables) {
255                     mFinishRunnables.remove(transition);
256                 }
257                 runner.onAnimationCancelled();
258             }
259         };
260     }
261 
262     /**
263      * Reset the alpha of the Launcher leash to give the Launcher time to hide its Views before the
264      * exit-desktop animation starts.
265      *
266      * This method should only be called if the current transition is opening Launcher, otherwise we
267      * might not be exiting Desktop Mode.
268      */
resetLauncherAlphaOnDesktopExit( TransitionInfo info, TransitionInfo.Change launcherChange, ArrayMap<SurfaceControl, SurfaceControl> leashMap, SurfaceControl.Transaction startTransaction )269     private static void resetLauncherAlphaOnDesktopExit(
270             TransitionInfo info,
271             TransitionInfo.Change launcherChange,
272             ArrayMap<SurfaceControl, SurfaceControl> leashMap,
273             SurfaceControl.Transaction startTransaction
274     ) {
275         checkArgument(isOpeningMode(launcherChange.getMode()));
276         if (!isClosingType(info.getType())
277                 && !ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX.isTrue()) {
278             return;
279         }
280         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
281             final TransitionInfo.Change change = info.getChanges().get(i);
282             // skip changes that we didn't wrap
283             if (!leashMap.containsKey(change.getLeash())) continue;
284             // Only make the update if we are closing Desktop tasks.
285             if (change.getTaskInfo() != null && (change.getTaskInfo().isFreeform()
286                     || change.hasFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY))
287                     && isClosingMode(change.getMode())) {
288                 startTransaction.setAlpha(leashMap.get(launcherChange.getLeash()), 0f);
289                 return;
290             }
291         }
292     }
293 }
294