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.view.WindowManager.TransitionOldType;
26 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
27 
28 import android.annotation.SuppressLint;
29 import android.app.IApplicationThread;
30 import android.os.IBinder;
31 import android.os.RemoteException;
32 import android.util.ArrayMap;
33 import android.util.Log;
34 import android.view.IRemoteAnimationFinishedCallback;
35 import android.view.IRemoteAnimationRunner;
36 import android.view.RemoteAnimationAdapter;
37 import android.view.RemoteAnimationTarget;
38 import android.view.SurfaceControl;
39 import android.window.IRemoteTransition;
40 import android.window.IRemoteTransitionFinishedCallback;
41 import android.window.RemoteTransition;
42 import android.window.TransitionInfo;
43 
44 import com.android.wm.shell.util.CounterRotator;
45 
46 /**
47  * @see RemoteAnimationAdapter
48  */
49 public class RemoteAnimationAdapterCompat {
50 
51     private final RemoteAnimationAdapter mWrapped;
52     private final RemoteTransitionCompat mRemoteTransition;
53 
RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration, long statusBarTransitionDelay, IApplicationThread appThread)54     public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
55             long statusBarTransitionDelay, IApplicationThread appThread) {
56         mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
57                 statusBarTransitionDelay);
58         mRemoteTransition = buildRemoteTransition(runner, appThread);
59     }
60 
getWrapped()61     RemoteAnimationAdapter getWrapped() {
62         return mWrapped;
63     }
64 
65     /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
buildRemoteTransition(RemoteAnimationRunnerCompat runner, IApplicationThread appThread)66     public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
67             IApplicationThread appThread) {
68         return new RemoteTransitionCompat(
69                 new RemoteTransition(wrapRemoteTransition(runner), appThread));
70     }
71 
getRemoteTransition()72     public RemoteTransitionCompat getRemoteTransition() {
73         return mRemoteTransition;
74     }
75 
76     /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
wrapRemoteAnimationRunner( final RemoteAnimationRunnerCompat remoteAnimationAdapter)77     public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
78             final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
79         return new IRemoteAnimationRunner.Stub() {
80             @Override
81             public void onAnimationStart(@TransitionOldType int transit,
82                     RemoteAnimationTarget[] apps,
83                     RemoteAnimationTarget[] wallpapers,
84                     RemoteAnimationTarget[] nonApps,
85                     final IRemoteAnimationFinishedCallback finishedCallback) {
86                 final RemoteAnimationTargetCompat[] appsCompat =
87                         RemoteAnimationTargetCompat.wrap(apps);
88                 final RemoteAnimationTargetCompat[] wallpapersCompat =
89                         RemoteAnimationTargetCompat.wrap(wallpapers);
90                 final RemoteAnimationTargetCompat[] nonAppsCompat =
91                         RemoteAnimationTargetCompat.wrap(nonApps);
92                 final Runnable animationFinishedCallback = new Runnable() {
93                     @Override
94                     public void run() {
95                         try {
96                             finishedCallback.onAnimationFinished();
97                         } catch (RemoteException e) {
98                             Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
99                                     + " finished callback", e);
100                         }
101                     }
102                 };
103                 remoteAnimationAdapter.onAnimationStart(transit, appsCompat, wallpapersCompat,
104                         nonAppsCompat, animationFinishedCallback);
105             }
106 
107             @Override
108             public void onAnimationCancelled() {
109                 remoteAnimationAdapter.onAnimationCancelled();
110             }
111         };
112     }
113 
114     private static IRemoteTransition.Stub wrapRemoteTransition(
115             final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
116         return new IRemoteTransition.Stub() {
117             @Override
118             public void startAnimation(IBinder token, TransitionInfo info,
119                     SurfaceControl.Transaction t,
120                     IRemoteTransitionFinishedCallback finishCallback) {
121                 final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
122                 final RemoteAnimationTargetCompat[] appsCompat =
123                         RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap);
124                 final RemoteAnimationTargetCompat[] wallpapersCompat =
125                         RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap);
126                 // TODO(bc-unlock): Build wrapped object for non-apps target.
127                 final RemoteAnimationTargetCompat[] nonAppsCompat =
128                         new RemoteAnimationTargetCompat[0];
129 
130                 // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
131                 boolean isReturnToHome = false;
132                 TransitionInfo.Change launcherTask = null;
133                 TransitionInfo.Change wallpaper = null;
134                 int launcherLayer = 0;
135                 int rotateDelta = 0;
136                 float displayW = 0;
137                 float displayH = 0;
138                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
139                     final TransitionInfo.Change change = info.getChanges().get(i);
140                     if (change.getTaskInfo() != null
141                             && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
142                         isReturnToHome = change.getMode() == TRANSIT_OPEN
143                                 || change.getMode() == TRANSIT_TO_FRONT;
144                         launcherTask = change;
145                         launcherLayer = info.getChanges().size() - i;
146                     } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
147                         wallpaper = change;
148                     }
149                     if (change.getParent() == null && change.getEndRotation() >= 0
150                             && change.getEndRotation() != change.getStartRotation()) {
151                         rotateDelta = change.getEndRotation() - change.getStartRotation();
152                         displayW = change.getEndAbsBounds().width();
153                         displayH = change.getEndAbsBounds().height();
154                     }
155                 }
156 
157                 // Prepare for rotation if there is one
158                 final CounterRotator counterLauncher = new CounterRotator();
159                 final CounterRotator counterWallpaper = new CounterRotator();
160                 if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
161                     counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
162                             rotateDelta, displayW, displayH);
163                     if (counterLauncher.getSurface() != null) {
164                         t.setLayer(counterLauncher.getSurface(), launcherLayer);
165                     }
166                 }
167 
168                 if (isReturnToHome) {
169                     if (counterLauncher.getSurface() != null) {
170                         t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
171                     }
172                     // Need to "boost" the closing things since that's what launcher expects.
173                     for (int i = info.getChanges().size() - 1; i >= 0; --i) {
174                         final TransitionInfo.Change change = info.getChanges().get(i);
175                         final SurfaceControl leash = leashMap.get(change.getLeash());
176                         final int mode = info.getChanges().get(i).getMode();
177                         // Only deal with independent layers
178                         if (!TransitionInfo.isIndependent(change, info)) continue;
179                         if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
180                             t.setLayer(leash, info.getChanges().size() * 3 - i);
181                             counterLauncher.addChild(t, leash);
182                         }
183                     }
184                     // Make wallpaper visible immediately since launcher apparently won't do this.
185                     for (int i = wallpapersCompat.length - 1; i >= 0; --i) {
186                         t.show(wallpapersCompat[i].leash.getSurfaceControl());
187                         t.setAlpha(wallpapersCompat[i].leash.getSurfaceControl(), 1.f);
188                     }
189                 } else {
190                     if (launcherTask != null) {
191                         counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
192                     }
193                     if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
194                         counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
195                                 rotateDelta, displayW, displayH);
196                         if (counterWallpaper.getSurface() != null) {
197                             t.setLayer(counterWallpaper.getSurface(), -1);
198                             counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
199                         }
200                     }
201                 }
202                 t.apply();
203 
204                 final Runnable animationFinishedCallback = new Runnable() {
205                     @Override
206                     @SuppressLint("NewApi")
207                     public void run() {
208                         try {
209                             counterLauncher.cleanUp(info.getRootLeash());
210                             counterWallpaper.cleanUp(info.getRootLeash());
211                             // Release surface references now. This is apparently to free GPU
212                             // memory while doing quick operations (eg. during CTS).
213                             for (int i = 0; i < info.getChanges().size(); ++i) {
214                                 info.getChanges().get(i).getLeash().release();
215                             }
216                             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
217                             for (int i = 0; i < leashMap.size(); ++i) {
218                                 if (leashMap.keyAt(i) == leashMap.valueAt(i)) continue;
219                                 t.remove(leashMap.valueAt(i));
220                             }
221                             t.apply();
222                             finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
223                         } catch (RemoteException e) {
224                             Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
225                                     + " finished callback", e);
226                         }
227                     }
228                 };
229                 // TODO(bc-unlcok): Pass correct transit type.
230                 remoteAnimationAdapter.onAnimationStart(
231                         TRANSIT_OLD_NONE,
232                         appsCompat, wallpapersCompat, nonAppsCompat,
233                         animationFinishedCallback);
234             }
235 
236             @Override
237             public void mergeAnimation(IBinder token, TransitionInfo info,
238                     SurfaceControl.Transaction t, IBinder mergeTarget,
239                     IRemoteTransitionFinishedCallback finishCallback) {
240                 // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, ignore
241                 //       any incoming merges.
242             }
243         };
244     }
245 }
246