1 /* 2 * Copyright (C) 2023 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.wm.shell.shared; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.view.RemoteAnimationTarget.MODE_CHANGING; 21 import static android.view.RemoteAnimationTarget.MODE_CLOSING; 22 import static android.view.RemoteAnimationTarget.MODE_OPENING; 23 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; 24 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; 25 import static android.view.WindowManager.TRANSIT_CHANGE; 26 import static android.view.WindowManager.TRANSIT_CLOSE; 27 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; 28 import static android.view.WindowManager.TRANSIT_OPEN; 29 import static android.view.WindowManager.TRANSIT_TO_BACK; 30 import static android.view.WindowManager.TRANSIT_TO_FRONT; 31 import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; 32 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 33 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 34 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 35 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; 36 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; 37 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 import android.annotation.SuppressLint; 41 import android.app.ActivityManager; 42 import android.app.WindowConfiguration; 43 import android.graphics.Rect; 44 import android.util.ArrayMap; 45 import android.util.SparseBooleanArray; 46 import android.view.RemoteAnimationTarget; 47 import android.view.SurfaceControl; 48 import android.view.WindowManager; 49 import android.window.TransitionInfo; 50 51 import java.util.function.Predicate; 52 53 /** Various utility functions for transitions. */ 54 public class TransitionUtil { 55 /** Flag applied to a transition change to identify it as a divider bar for animation. */ 56 public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; 57 58 /** @return true if the transition was triggered by opening something vs closing something */ isOpeningType(@indowManager.TransitionType int type)59 public static boolean isOpeningType(@WindowManager.TransitionType int type) { 60 return type == TRANSIT_OPEN 61 || type == TRANSIT_TO_FRONT 62 || type == TRANSIT_KEYGUARD_GOING_AWAY; 63 } 64 65 /** @return true if the transition was triggered by closing something vs opening something */ isClosingType(@indowManager.TransitionType int type)66 public static boolean isClosingType(@WindowManager.TransitionType int type) { 67 return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK; 68 } 69 70 /** Returns {@code true} if the transition is opening or closing mode. */ isOpenOrCloseMode(@ransitionInfo.TransitionMode int mode)71 public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { 72 return isOpeningMode(mode) || isClosingMode(mode); 73 } 74 75 /** Returns {@code true} if the transition is opening mode. */ isOpeningMode(@ransitionInfo.TransitionMode int mode)76 public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) { 77 return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT; 78 } 79 80 /** Returns {@code true} if the transition is closing mode. */ isClosingMode(@ransitionInfo.TransitionMode int mode)81 public static boolean isClosingMode(@TransitionInfo.TransitionMode int mode) { 82 return mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; 83 } 84 85 /** Returns {@code true} if the transition has a display change. */ hasDisplayChange(@onNull TransitionInfo info)86 public static boolean hasDisplayChange(@NonNull TransitionInfo info) { 87 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 88 final TransitionInfo.Change change = info.getChanges().get(i); 89 if (change.getMode() == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) { 90 return true; 91 } 92 } 93 return false; 94 } 95 96 /** Returns `true` if `change` is a wallpaper. */ isWallpaper(TransitionInfo.Change change)97 public static boolean isWallpaper(TransitionInfo.Change change) { 98 return (change.getTaskInfo() == null) 99 && change.hasFlags(FLAG_IS_WALLPAPER) 100 && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); 101 } 102 103 /** Returns `true` if `change` is not an app window or wallpaper. */ isNonApp(TransitionInfo.Change change)104 public static boolean isNonApp(TransitionInfo.Change change) { 105 return (change.getTaskInfo() == null) 106 && !change.hasFlags(FLAG_IS_WALLPAPER) 107 && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); 108 } 109 110 /** Returns `true` if `change` is the divider. */ isDividerBar(TransitionInfo.Change change)111 public static boolean isDividerBar(TransitionInfo.Change change) { 112 return isNonApp(change) && change.hasFlags(FLAG_IS_DIVIDER_BAR); 113 } 114 115 /** Returns `true` if `change` is only re-ordering. */ isOrderOnly(TransitionInfo.Change change)116 public static boolean isOrderOnly(TransitionInfo.Change change) { 117 return change.getMode() == TRANSIT_CHANGE 118 && (change.getFlags() & FLAG_MOVED_TO_TOP) != 0 119 && change.getStartAbsBounds().equals(change.getEndAbsBounds()) 120 && (change.getLastParent() == null 121 || change.getLastParent().equals(change.getParent())); 122 } 123 124 /** 125 * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you 126 * MUST call `test` in the same order that the changes appear in the TransitionInfo. 127 */ 128 public static class LeafTaskFilter implements Predicate<TransitionInfo.Change> { 129 private final SparseBooleanArray mChildTaskTargets = new SparseBooleanArray(); 130 131 @Override test(TransitionInfo.Change change)132 public boolean test(TransitionInfo.Change change) { 133 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); 134 if (taskInfo == null) return false; 135 // Children always come before parent since changes are in top-to-bottom z-order. 136 boolean hasChildren = mChildTaskTargets.get(taskInfo.taskId); 137 if (taskInfo.hasParentTask()) { 138 mChildTaskTargets.put(taskInfo.parentTaskId, true); 139 } 140 // If it has children, it's not a leaf. 141 return !hasChildren; 142 } 143 } 144 145 newModeToLegacyMode(int newMode)146 private static int newModeToLegacyMode(int newMode) { 147 switch (newMode) { 148 case WindowManager.TRANSIT_OPEN: 149 case WindowManager.TRANSIT_TO_FRONT: 150 return MODE_OPENING; 151 case WindowManager.TRANSIT_CLOSE: 152 case WindowManager.TRANSIT_TO_BACK: 153 return MODE_CLOSING; 154 default: 155 return MODE_CHANGING; 156 } 157 } 158 159 /** 160 * Very similar to Transitions#setupAnimHierarchy but specialized for leashes. 161 */ 162 @SuppressLint("NewApi") setupLeash(@onNull SurfaceControl leash, @NonNull TransitionInfo.Change change, int layer, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t)163 private static void setupLeash(@NonNull SurfaceControl leash, 164 @NonNull TransitionInfo.Change change, int layer, 165 @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { 166 final boolean isOpening = TransitionUtil.isOpeningType(info.getType()); 167 // Put animating stuff above this line and put static stuff below it. 168 int zSplitLine = info.getChanges().size(); 169 // changes should be ordered top-to-bottom in z 170 final int mode = change.getMode(); 171 172 final int rootIdx = TransitionUtil.rootIndexFor(change, info); 173 t.reparent(leash, info.getRoot(rootIdx).getLeash()); 174 final Rect absBounds = 175 (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds(); 176 t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x, 177 absBounds.top - info.getRoot(rootIdx).getOffset().y); 178 179 if (isDividerBar(change)) { 180 if (isOpeningType(mode)) { 181 t.setAlpha(leash, 0.f); 182 } 183 // Set the transition leash position to 0 in case the divider leash position being 184 // taking down. 185 t.setPosition(leash, 0, 0); 186 t.setLayer(leash, Integer.MAX_VALUE); 187 return; 188 } 189 190 // Put all the OPEN/SHOW on top 191 if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { 192 // Wallpaper is always at the bottom, opening wallpaper on top of closing one. 193 if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) { 194 t.setLayer(leash, -zSplitLine + info.getChanges().size() - layer); 195 } else { 196 t.setLayer(leash, -zSplitLine - layer); 197 } 198 } else if (TransitionUtil.isOpeningType(mode)) { 199 if (isOpening) { 200 t.setLayer(leash, zSplitLine + info.getChanges().size() - layer); 201 if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) { 202 // if transferred, it should be left visible. 203 t.setAlpha(leash, 0.f); 204 } 205 } else { 206 // put on bottom and leave it visible 207 t.setLayer(leash, zSplitLine - layer); 208 } 209 } else if (TransitionUtil.isClosingType(mode)) { 210 if (isOpening) { 211 // put on bottom and leave visible 212 t.setLayer(leash, zSplitLine - layer); 213 } else { 214 // put on top 215 t.setLayer(leash, zSplitLine + info.getChanges().size() - layer); 216 } 217 } else { // CHANGE 218 t.setLayer(leash, zSplitLine + info.getChanges().size() - layer); 219 } 220 } 221 222 @SuppressLint("NewApi") createLeash(TransitionInfo info, TransitionInfo.Change change, int order, SurfaceControl.Transaction t)223 private static SurfaceControl createLeash(TransitionInfo info, TransitionInfo.Change change, 224 int order, SurfaceControl.Transaction t) { 225 // TODO: once we can properly sync transactions across process, then get rid of this leash. 226 if (change.getParent() != null && (change.getFlags() & FLAG_IS_WALLPAPER) != 0) { 227 // Special case for wallpaper atm. Normally these are left alone; but, a quirk of 228 // making leashes means we have to handle them specially. 229 return change.getLeash(); 230 } 231 final int rootIdx = TransitionUtil.rootIndexFor(change, info); 232 SurfaceControl leashSurface = new SurfaceControl.Builder() 233 .setName(change.getLeash().toString() + "_transition-leash") 234 .setContainerLayer() 235 // Initial the surface visible to respect the visibility of the original surface. 236 .setHidden(false) 237 .setParent(info.getRoot(rootIdx).getLeash()) 238 .build(); 239 // Copied Transitions setup code (which expects bottom-to-top order, so we swap here) 240 setupLeash(leashSurface, change, info.getChanges().size() - order, info, t); 241 t.reparent(change.getLeash(), leashSurface); 242 t.setAlpha(change.getLeash(), 1.0f); 243 t.show(change.getLeash()); 244 if (!isDividerBar(change)) { 245 // For divider, don't modify its inner leash position when creating the outer leash 246 // for the transition. In case the position being wrong after the transition finished. 247 t.setPosition(change.getLeash(), 0, 0); 248 } 249 t.setLayer(change.getLeash(), 0); 250 return leashSurface; 251 } 252 253 /** 254 * Creates a new RemoteAnimationTarget from the provided change info 255 */ newTarget(TransitionInfo.Change change, int order, TransitionInfo info, SurfaceControl.Transaction t, @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap)256 public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order, 257 TransitionInfo info, SurfaceControl.Transaction t, 258 @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) { 259 return newTarget(change, order, false /* forceTranslucent */, info, t, leashMap); 260 } 261 262 /** 263 * Creates a new RemoteAnimationTarget from the provided change info 264 */ newTarget(TransitionInfo.Change change, int order, boolean forceTranslucent, TransitionInfo info, SurfaceControl.Transaction t, @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap)265 public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order, 266 boolean forceTranslucent, TransitionInfo info, SurfaceControl.Transaction t, 267 @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) { 268 final SurfaceControl leash = createLeash(info, change, order, t); 269 if (leashMap != null) { 270 leashMap.put(change.getLeash(), leash); 271 } 272 return newTarget(change, order, leash, forceTranslucent); 273 } 274 275 /** 276 * Creates a new RemoteAnimationTarget from the provided change and leash 277 */ newTarget(TransitionInfo.Change change, int order, SurfaceControl leash)278 public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order, 279 SurfaceControl leash) { 280 return newTarget(change, order, leash, false /* forceTranslucent */); 281 } 282 283 /** 284 * Creates a new RemoteAnimationTarget from the provided change and leash 285 */ newTarget(TransitionInfo.Change change, int order, SurfaceControl leash, boolean forceTranslucent)286 public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order, 287 SurfaceControl leash, boolean forceTranslucent) { 288 if (isDividerBar(change)) { 289 return getDividerTarget(change, leash); 290 } 291 292 int taskId; 293 boolean isNotInRecents; 294 ActivityManager.RunningTaskInfo taskInfo; 295 WindowConfiguration windowConfiguration; 296 297 taskInfo = change.getTaskInfo(); 298 if (taskInfo != null) { 299 taskId = taskInfo.taskId; 300 isNotInRecents = !taskInfo.isRunning; 301 windowConfiguration = taskInfo.configuration.windowConfiguration; 302 } else { 303 taskId = INVALID_TASK_ID; 304 isNotInRecents = true; 305 windowConfiguration = new WindowConfiguration(); 306 } 307 308 Rect localBounds = new Rect(change.getEndAbsBounds()); 309 localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y); 310 311 RemoteAnimationTarget target = new RemoteAnimationTarget( 312 taskId, 313 newModeToLegacyMode(change.getMode()), 314 // TODO: once we can properly sync transactions across process, 315 // then get rid of this leash. 316 leash, 317 forceTranslucent || (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0, 318 null, 319 // TODO(shell-transitions): we need to send content insets? evaluate how its used. 320 new Rect(0, 0, 0, 0), 321 order, 322 null, 323 localBounds, 324 new Rect(change.getEndAbsBounds()), 325 windowConfiguration, 326 isNotInRecents, 327 null, 328 new Rect(change.getStartAbsBounds()), 329 taskInfo, 330 change.isAllowEnterPip(), 331 INVALID_WINDOW_TYPE 332 ); 333 target.setWillShowImeOnTarget( 334 (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0); 335 target.setRotationChange(change.getEndRotation() - change.getStartRotation()); 336 target.backgroundColor = change.getBackgroundColor(); 337 return target; 338 } 339 getDividerTarget(TransitionInfo.Change change, SurfaceControl leash)340 private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change, 341 SurfaceControl leash) { 342 return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()), 343 leash, false /* isTranslucent */, null /* clipRect */, 344 null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, 345 new android.graphics.Point(0, 0) /* position */, change.getStartAbsBounds(), 346 change.getStartAbsBounds(), new WindowConfiguration(), true, null /* startLeash */, 347 null /* startBounds */, null /* taskInfo */, false /* allowEnterPip */, 348 TYPE_DOCK_DIVIDER); 349 } 350 351 /** 352 * Finds the "correct" root idx for a change. The change's end display is prioritized, then 353 * the start display. If there is no display, it will fallback on the 0th root in the 354 * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op). 355 */ rootIndexFor(@onNull TransitionInfo.Change change, @NonNull TransitionInfo info)356 public static int rootIndexFor(@NonNull TransitionInfo.Change change, 357 @NonNull TransitionInfo info) { 358 int rootIdx = info.findRootIndex(change.getEndDisplayId()); 359 if (rootIdx >= 0) return rootIdx; 360 rootIdx = info.findRootIndex(change.getStartDisplayId()); 361 if (rootIdx >= 0) return rootIdx; 362 return 0; 363 } 364 365 /** 366 * Gets the {@link TransitionInfo.Root} for the given {@link TransitionInfo.Change}. 367 * @see #rootIndexFor(TransitionInfo.Change, TransitionInfo) 368 */ 369 @NonNull getRootFor(@onNull TransitionInfo.Change change, @NonNull TransitionInfo info)370 public static TransitionInfo.Root getRootFor(@NonNull TransitionInfo.Change change, 371 @NonNull TransitionInfo info) { 372 return info.getRoot(rootIndexFor(change, info)); 373 } 374 } 375