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.TransitionInfo.FLAG_IS_WALLPAPER;
26 
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 import android.view.IRemoteAnimationFinishedCallback;
32 import android.view.IRemoteAnimationRunner;
33 import android.view.RemoteAnimationTarget;
34 import android.view.SurfaceControl;
35 import android.view.WindowManager;
36 import android.view.WindowManager.TransitionOldType;
37 import android.window.IRemoteTransition;
38 import android.window.IRemoteTransitionFinishedCallback;
39 import android.window.RemoteTransitionStub;
40 import android.window.TransitionInfo;
41 
42 import com.android.wm.shell.shared.CounterRotator;
43 
44 public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
45     private static final String TAG = "RemoteAnimRunnerCompat";
46 
onAnimationStart(@indowManager.TransitionOldType int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback)47     public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
48             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
49             RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
50 
51     @Override
onAnimationStart(@ransitionOldType int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, final IRemoteAnimationFinishedCallback finishedCallback)52     public final void onAnimationStart(@TransitionOldType int transit,
53             RemoteAnimationTarget[] apps,
54             RemoteAnimationTarget[] wallpapers,
55             RemoteAnimationTarget[] nonApps,
56             final IRemoteAnimationFinishedCallback finishedCallback) {
57 
58         onAnimationStart(transit, apps, wallpapers,
59                 nonApps, () -> {
60                     try {
61                         finishedCallback.onAnimationFinished();
62                     } catch (RemoteException e) {
63                         Log.e(TAG, "Failed to call app controlled animation finished callback", e);
64                     }
65                 });
66     }
67 
toRemoteTransition()68     public IRemoteTransition toRemoteTransition() {
69         return wrap(this);
70     }
71 
72     /** Wraps a remote animation runner in a remote-transition. */
wrap(IRemoteAnimationRunner runner)73     public static RemoteTransitionStub wrap(IRemoteAnimationRunner runner) {
74         return new RemoteTransitionStub() {
75             final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
76 
77             @Override
78             public void startAnimation(IBinder token, TransitionInfo info,
79                     SurfaceControl.Transaction t,
80                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
81                 final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
82                 final RemoteAnimationTarget[] apps =
83                         RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
84                 final RemoteAnimationTarget[] wallpapers =
85                         RemoteAnimationTargetCompat.wrapNonApps(
86                                 info, true /* wallpapers */, t, leashMap);
87                 final RemoteAnimationTarget[] nonApps =
88                         RemoteAnimationTargetCompat.wrapNonApps(
89                                 info, false /* wallpapers */, t, leashMap);
90 
91                 // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
92                 boolean isReturnToHome = false;
93                 TransitionInfo.Change launcherTask = null;
94                 TransitionInfo.Change wallpaper = null;
95                 int launcherLayer = 0;
96                 int rotateDelta = 0;
97                 float displayW = 0;
98                 float displayH = 0;
99                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
100                     final TransitionInfo.Change change = info.getChanges().get(i);
101                     // skip changes that we didn't wrap
102                     if (!leashMap.containsKey(change.getLeash())) continue;
103                     if (change.getTaskInfo() != null
104                             && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
105                         isReturnToHome = change.getMode() == TRANSIT_OPEN
106                                 || change.getMode() == TRANSIT_TO_FRONT;
107                         launcherTask = change;
108                         launcherLayer = info.getChanges().size() - i;
109                     } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
110                         wallpaper = change;
111                     }
112                     if (change.getParent() == null && change.getEndRotation() >= 0
113                             && change.getEndRotation() != change.getStartRotation()) {
114                         rotateDelta = change.getEndRotation() - change.getStartRotation();
115                         displayW = change.getEndAbsBounds().width();
116                         displayH = change.getEndAbsBounds().height();
117                     }
118                 }
119 
120                 // Prepare for rotation if there is one
121                 final CounterRotator counterLauncher = new CounterRotator();
122                 final CounterRotator counterWallpaper = new CounterRotator();
123                 if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
124                     final TransitionInfo.Change parent = info.getChange(launcherTask.getParent());
125                     if (parent != null) {
126                         counterLauncher.setup(t, parent.getLeash(), rotateDelta, displayW,
127                                 displayH);
128                     } else {
129                         Log.e(TAG, "Malformed: " + launcherTask + " has parent="
130                                 + launcherTask.getParent() + " but it's not in info.");
131                     }
132                     if (counterLauncher.getSurface() != null) {
133                         t.setLayer(counterLauncher.getSurface(), launcherLayer);
134                     }
135                 }
136 
137                 if (isReturnToHome) {
138                     if (counterLauncher.getSurface() != null) {
139                         t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
140                     }
141                     // Need to "boost" the closing things since that's what launcher expects.
142                     for (int i = info.getChanges().size() - 1; i >= 0; --i) {
143                         final TransitionInfo.Change change = info.getChanges().get(i);
144                         final SurfaceControl leash = leashMap.get(change.getLeash());
145                         // skip changes that we didn't wrap
146                         if (leash == null) continue;
147                         final int mode = info.getChanges().get(i).getMode();
148                         // Only deal with independent layers
149                         if (!TransitionInfo.isIndependent(change, info)) continue;
150                         if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
151                             t.setLayer(leash, info.getChanges().size() * 3 - i);
152                             counterLauncher.addChild(t, leash);
153                         }
154                     }
155                     // Make wallpaper visible immediately since launcher apparently won't do this.
156                     for (int i = wallpapers.length - 1; i >= 0; --i) {
157                         t.show(wallpapers[i].leash);
158                         t.setAlpha(wallpapers[i].leash, 1.f);
159                     }
160                 } else {
161                     if (launcherTask != null) {
162                         counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
163                     }
164                     if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
165                         final TransitionInfo.Change parent = info.getChange(wallpaper.getParent());
166                         if (parent != null) {
167                             counterWallpaper.setup(t, parent.getLeash(), rotateDelta, displayW,
168                                     displayH);
169                         } else {
170                             Log.e(TAG, "Malformed: " + wallpaper + " has parent="
171                                     + wallpaper.getParent() + " but it's not in info.");
172                         }
173                         if (counterWallpaper.getSurface() != null) {
174                             t.setLayer(counterWallpaper.getSurface(), -1);
175                             counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
176                         }
177                     }
178                 }
179                 t.apply();
180 
181                 final Runnable animationFinishedCallback = () -> {
182                     final SurfaceControl.Transaction finishTransaction =
183                             new SurfaceControl.Transaction();
184                     counterLauncher.cleanUp(finishTransaction);
185                     counterWallpaper.cleanUp(finishTransaction);
186                     // Release surface references now. This is apparently to free GPU memory
187                     // before GC would.
188                     info.releaseAllSurfaces();
189                     // Don't release here since launcher might still be using them. Instead
190                     // let launcher release them (eg. via RemoteAnimationTargets)
191                     leashMap.clear();
192                     try {
193                         finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
194                         finishTransaction.close();
195                     } catch (RemoteException e) {
196                         Log.e(TAG, "Failed to call app controlled animation finished callback", e);
197                     }
198                 };
199                 synchronized (mFinishRunnables) {
200                     mFinishRunnables.put(token, animationFinishedCallback);
201                 }
202                 // TODO(bc-unlcok): Pass correct transit type.
203                 runner.onAnimationStart(TRANSIT_OLD_NONE,
204                         apps, wallpapers, nonApps, new IRemoteAnimationFinishedCallback() {
205                             @Override
206                             public void onAnimationFinished() {
207                                 synchronized (mFinishRunnables) {
208                                     if (mFinishRunnables.remove(token) == null) return;
209                                 }
210                                 animationFinishedCallback.run();
211                             }
212 
213                             @Override
214                             public IBinder asBinder() {
215                                 return null;
216                             }
217                         });
218             }
219 
220             @Override
221             public void mergeAnimation(IBinder token, TransitionInfo info,
222                     SurfaceControl.Transaction t, IBinder mergeTarget,
223                     IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
224                 // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
225                 //       to legacy cancel.
226                 final Runnable finishRunnable;
227                 synchronized (mFinishRunnables) {
228                     finishRunnable = mFinishRunnables.remove(mergeTarget);
229                 }
230                 // Since we're not actually animating, release native memory now
231                 t.close();
232                 info.releaseAllSurfaces();
233                 if (finishRunnable == null) return;
234                 runner.onAnimationCancelled();
235                 finishRunnable.run();
236             }
237         };
238     }
239 }
240