• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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