• 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 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