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