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