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