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