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