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