1 /* 2 * Copyright (C) 2021 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.quickstep; 18 19 import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher; 20 import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; 21 22 import android.app.WindowConfiguration; 23 import android.content.Context; 24 import android.graphics.Rect; 25 import android.util.Log; 26 import android.view.RemoteAnimationTarget; 27 import android.window.TransitionInfo; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.launcher3.statehandlers.DesktopVisibilityController; 33 import com.android.launcher3.util.SplitConfigurationOptions; 34 import com.android.quickstep.util.AnimatorControllerWithResistance; 35 import com.android.quickstep.util.TaskViewSimulator; 36 import com.android.quickstep.util.TransformParams; 37 import com.android.wm.shell.shared.split.SplitBounds; 38 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.List; 43 44 /** 45 * Glues together the necessary components to animate a remote target using a 46 * {@link TaskViewSimulator} 47 */ 48 public class RemoteTargetGluer { 49 private static final String TAG = "RemoteTargetGluer"; 50 51 // This is the default number of handles to create when we don't know how many tasks are running 52 // (e.g. if we're in split screen). Allocate extra for potential tasks overlaid, like volume. 53 private static final int DEFAULT_NUM_HANDLES = 4; 54 55 private RemoteTargetHandle[] mRemoteTargetHandles; 56 private SplitConfigurationOptions.SplitBounds mSplitBounds; 57 58 /** 59 * Use this constructor if remote targets are split-screen independent 60 */ RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy, RemoteAnimationTargets targets, boolean forDesktop)61 public RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy, 62 RemoteAnimationTargets targets, boolean forDesktop) { 63 init(context, sizingStrategy, targets.apps.length, forDesktop); 64 } 65 66 /** 67 * Use this constructor if you want the number of handles created to match the number of active 68 * running tasks 69 */ RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy)70 public RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy) { 71 // TODO: b/403344864 Make sure init with correct number of RemoteTargetHandle with 72 // multi-desks feature enabled as well. 73 int visibleTasksCount = DesktopVisibilityController.INSTANCE.get(context) 74 .getVisibleDesktopTasksCountDeprecated(); 75 if (visibleTasksCount > 0) { 76 // Allocate +1 to account for a new task added to the desktop mode 77 int numHandles = visibleTasksCount + 1; 78 init(context, sizingStrategy, numHandles, true /* forDesktop */); 79 return; 80 } 81 82 // Assume 2 handles needed for split, scale down as needed later on when we actually 83 // get remote targets 84 init(context, sizingStrategy, DEFAULT_NUM_HANDLES, false /* forDesktop */); 85 } 86 init(Context context, BaseContainerInterface sizingStrategy, int numHandles, boolean forDesktop)87 private void init(Context context, BaseContainerInterface sizingStrategy, int numHandles, 88 boolean forDesktop) { 89 mRemoteTargetHandles = createHandles(context, sizingStrategy, numHandles, forDesktop); 90 } 91 createHandles(Context context, BaseContainerInterface sizingStrategy, int numHandles, boolean forDesktop)92 private RemoteTargetHandle[] createHandles(Context context, 93 BaseContainerInterface sizingStrategy, int numHandles, boolean forDesktop) { 94 RemoteTargetHandle[] handles = new RemoteTargetHandle[numHandles]; 95 for (int i = 0; i < numHandles; i++) { 96 TaskViewSimulator tvs = new TaskViewSimulator(context, sizingStrategy, forDesktop , i); 97 TransformParams transformParams = new TransformParams(); 98 handles[i] = new RemoteTargetHandle(tvs, transformParams); 99 } 100 return handles; 101 } 102 103 /** 104 * Pairs together {@link TaskViewSimulator}s and {@link TransformParams} into a 105 * {@link RemoteTargetHandle} 106 * Assigns only the apps associated with {@param targets} into their own TaskViewSimulators. 107 * Length of targets.apps should match that of {@link #mRemoteTargetHandles}. 108 * 109 * If split screen may be active when this is called, you might want to use 110 * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)} 111 */ assignTargets(RemoteAnimationTargets targets)112 public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) { 113 for (int i = 0; i < mRemoteTargetHandles.length; i++) { 114 RemoteAnimationTarget primaryTaskTarget = targets.apps[i]; 115 mRemoteTargetHandles[i].mTransformParams.setTargetSet( 116 createRemoteAnimationTargetsForTarget(targets, Collections.emptyList())); 117 mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null); 118 } 119 return mRemoteTargetHandles; 120 } 121 122 /** 123 * Calls {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)} with SplitBounds 124 * information specified. 125 */ assignTargetsForSplitScreen(RemoteAnimationTargets targets, SplitConfigurationOptions.SplitBounds splitBounds)126 public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets, 127 SplitConfigurationOptions.SplitBounds splitBounds) { 128 mSplitBounds = splitBounds; 129 return assignTargetsForSplitScreen(targets); 130 } 131 132 /** 133 * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this assigns the 134 * apps in {@code targets.apps} to the {@link #mRemoteTargetHandles} with index 0 will being 135 * the left/top task, index 1 right/bottom. 136 */ assignTargetsForSplitScreen(RemoteAnimationTargets targets)137 public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) { 138 resizeRemoteTargetHandles(targets); 139 140 // If we are in a true split screen case (2 apps running on screen), either: 141 // a) mSplitBounds was already set (from the clicked GroupedTaskView) 142 // b) A SplitBounds was passed up from shell (via AbsSwipeUpHandler) 143 // If both of these are null, we are in a 1-app or 1-app-plus-assistant case. 144 if (mSplitBounds == null) { 145 SplitBounds shellSplitBounds = targets.extras.getParcelable(KEY_EXTRA_SPLIT_BOUNDS, 146 SplitBounds.class); 147 if (shellSplitBounds != null) { 148 mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds); 149 } 150 } 151 152 boolean containsSplitTargets = mSplitBounds != null; 153 Log.d(TAG, "containsSplitTargets? " + containsSplitTargets + " handleLength: " + 154 mRemoteTargetHandles.length + " appsLength: " + targets.apps.length); 155 156 if (mRemoteTargetHandles.length == 1) { 157 // Single fullscreen app 158 159 // If we're not in split screen, the splitIds count doesn't really matter since we 160 // should always hit this case. 161 mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets); 162 if (targets.apps.length > 0) { 163 // Unclear why/when target.apps length == 0, but it sure does happen :( 164 mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(targets.apps[0], null); 165 } 166 } else if (!containsSplitTargets) { 167 // Single App + Assistant 168 for (int i = 0; i < mRemoteTargetHandles.length; i++) { 169 mRemoteTargetHandles[i].mTransformParams.setTargetSet(targets); 170 mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(targets.apps[i], null); 171 } 172 } else { 173 // Split apps (+ maybe assistant) 174 RemoteAnimationTarget topLeftTarget = targets.findTask(mSplitBounds.leftTopTaskId); 175 RemoteAnimationTarget bottomRightTarget = targets.findTask( 176 mSplitBounds.rightBottomTaskId); 177 List<RemoteAnimationTarget> overlayTargets = Arrays.stream(targets.apps).filter( 178 target -> target.windowConfiguration.getWindowingMode() 179 != WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW).toList(); 180 181 // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude, 182 // vice versa 183 mRemoteTargetHandles[0].mTransformParams.setTargetSet( 184 createRemoteAnimationTargetsForTarget(targets, 185 Collections.singletonList(bottomRightTarget))); 186 mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, mSplitBounds); 187 188 mRemoteTargetHandles[1].mTransformParams.setTargetSet( 189 createRemoteAnimationTargetsForTarget(targets, 190 Collections.singletonList(topLeftTarget))); 191 mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(bottomRightTarget, mSplitBounds); 192 193 // Set the remaining overlay tasks to be their own TaskViewSimulator as fullscreen tasks 194 if (!overlayTargets.isEmpty()) { 195 ArrayList<RemoteAnimationTarget> targetsToExclude = new ArrayList<>(); 196 targetsToExclude.add(topLeftTarget); 197 targetsToExclude.add(bottomRightTarget); 198 // Start i at 2 to account for top/left and bottom/right split handles already made 199 for (int i = 2; i < targets.apps.length; i++) { 200 if (i >= mRemoteTargetHandles.length) { 201 Log.e(TAG, String.format("Attempting to animate an untracked target" 202 + " (%d handles allocated, but %d want to animate)", 203 mRemoteTargetHandles.length, targets.apps.length)); 204 break; 205 } 206 mRemoteTargetHandles[i].mTransformParams.setTargetSet( 207 createRemoteAnimationTargetsForTarget(targets, targetsToExclude)); 208 mRemoteTargetHandles[i].mTaskViewSimulator.setPreview( 209 overlayTargets.get(i - 2)); 210 } 211 212 } 213 } 214 return mRemoteTargetHandles; 215 } 216 217 /** 218 * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this creates distinct 219 * transform params per app in {@code targets.apps} list. 220 */ assignTargetsForDesktop( RemoteAnimationTargets targets, @Nullable TransitionInfo transitionInfo)221 public RemoteTargetHandle[] assignTargetsForDesktop( 222 RemoteAnimationTargets targets, @Nullable TransitionInfo transitionInfo) { 223 resizeRemoteTargetHandles(targets); 224 225 for (int i = 0; i < mRemoteTargetHandles.length; i++) { 226 RemoteAnimationTarget primaryTaskTarget = targets.apps[i]; 227 List<RemoteAnimationTarget> excludeTargets = Arrays.stream(targets.apps) 228 .filter(target -> target.taskId != primaryTaskTarget.taskId).toList(); 229 mRemoteTargetHandles[i].mTransformParams.setTargetSet( 230 createRemoteAnimationTargetsForTarget(targets, excludeTargets)); 231 mRemoteTargetHandles[i].mTransformParams.setTransitionInfo(transitionInfo); 232 mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null); 233 } 234 return mRemoteTargetHandles; 235 } 236 237 /** 238 * Resize the `mRemoteTargetHandles` array since we assumed initial size, but 239 * `targets.apps` is the ultimate source of truth here 240 */ resizeRemoteTargetHandles(RemoteAnimationTargets targets)241 private void resizeRemoteTargetHandles(RemoteAnimationTargets targets) { 242 long appCount = Arrays.stream(targets.apps) 243 .filter(app -> app.mode == targets.targetMode) 244 .count(); 245 Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length); 246 if (appCount < mRemoteTargetHandles.length) { 247 Log.d(TAG, "resizing handles"); 248 RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount]; 249 System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount); 250 mRemoteTargetHandles = newHandles; 251 } 252 } 253 getStartBounds(RemoteAnimationTarget target)254 private Rect getStartBounds(RemoteAnimationTarget target) { 255 return target.startBounds == null ? target.screenSpaceBounds : target.startBounds; 256 } 257 258 /** 259 * Ensures that we aren't excluding ancillary targets such as home/recents 260 * 261 * @param targetsToExclude Will be excluded from the resulting return value. 262 * Pass in an empty list to not exclude anything 263 * @return RemoteAnimationTargets where all the app targets from the passed in 264 * {@code targets} are included except {@code targetsToExclude} 265 */ createRemoteAnimationTargetsForTarget( @onNull RemoteAnimationTargets targets, @NonNull List<RemoteAnimationTarget> targetsToExclude)266 private RemoteAnimationTargets createRemoteAnimationTargetsForTarget( 267 @NonNull RemoteAnimationTargets targets, 268 @NonNull List<RemoteAnimationTarget> targetsToExclude) { 269 ArrayList<RemoteAnimationTarget> targetsToInclude = new ArrayList<>(); 270 271 for (RemoteAnimationTarget targetCompat : targets.unfilteredApps) { 272 boolean skipTarget = false; 273 for (RemoteAnimationTarget excludingTarget : targetsToExclude) { 274 if (targetCompat == excludingTarget) { 275 skipTarget = true; 276 break; 277 } 278 if (excludingTarget != null 279 && excludingTarget.taskInfo != null 280 && targetCompat.taskInfo != null 281 && excludingTarget.taskInfo.parentTaskId == targetCompat.taskInfo.taskId) { 282 // Also exclude corresponding parent task 283 skipTarget = true; 284 } 285 } 286 if (skipTarget) { 287 continue; 288 } 289 targetsToInclude.add(targetCompat); 290 } 291 final RemoteAnimationTarget[] filteredApps = targetsToInclude.toArray( 292 new RemoteAnimationTarget[0]); 293 return new RemoteAnimationTargets( 294 filteredApps, targets.wallpapers, targets.nonApps, targets.targetMode); 295 } 296 297 /** 298 * The object returned by this is may be modified in 299 * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}, specifically the length of the 300 * array may be shortened based on the number of RemoteAnimationTargets present. 301 * <p> 302 * This can be accessed at any time, however the count will be more accurate if accessed after 303 * calling one of the respective assignTargets*() methods 304 */ getRemoteTargetHandles()305 public RemoteTargetHandle[] getRemoteTargetHandles() { 306 return mRemoteTargetHandles; 307 } 308 getSplitBounds()309 public SplitConfigurationOptions.SplitBounds getSplitBounds() { 310 return mSplitBounds; 311 } 312 313 /** 314 * Container to keep together all the associated objects whose properties need to be updated to 315 * animate a single remote app target 316 */ 317 public static class RemoteTargetHandle { 318 private final TaskViewSimulator mTaskViewSimulator; 319 private final TransformParams mTransformParams; 320 @Nullable 321 private AnimatorControllerWithResistance mPlaybackController; 322 RemoteTargetHandle(TaskViewSimulator taskViewSimulator, TransformParams transformParams)323 public RemoteTargetHandle(TaskViewSimulator taskViewSimulator, 324 TransformParams transformParams) { 325 mTransformParams = transformParams; 326 mTaskViewSimulator = taskViewSimulator; 327 } 328 getTaskViewSimulator()329 public TaskViewSimulator getTaskViewSimulator() { 330 return mTaskViewSimulator; 331 } 332 getTransformParams()333 public TransformParams getTransformParams() { 334 return mTransformParams; 335 } 336 337 @Nullable getPlaybackController()338 public AnimatorControllerWithResistance getPlaybackController() { 339 return mPlaybackController; 340 } 341 setPlaybackController( @ullable AnimatorControllerWithResistance playbackController)342 public void setPlaybackController( 343 @Nullable AnimatorControllerWithResistance playbackController) { 344 mPlaybackController = playbackController; 345 } 346 } 347 } 348