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