• 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 androidx.window.extensions.embedding;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
20 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
21 
22 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
23 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
24 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
25 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
26 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
27 
28 import android.app.Activity;
29 import android.app.WindowConfiguration.WindowingMode;
30 import android.content.Intent;
31 import android.graphics.Rect;
32 import android.os.Bundle;
33 import android.os.IBinder;
34 import android.util.ArrayMap;
35 import android.window.TaskFragmentAnimationParams;
36 import android.window.TaskFragmentCreationParams;
37 import android.window.TaskFragmentInfo;
38 import android.window.TaskFragmentOperation;
39 import android.window.TaskFragmentOrganizer;
40 import android.window.TaskFragmentTransaction;
41 import android.window.WindowContainerTransaction;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import java.util.Map;
49 import java.util.concurrent.Executor;
50 
51 /**
52  * Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize
53  * task fragments.
54  *
55  * All calls into methods of this class are expected to be on the UI thread.
56  */
57 class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
58 
59     /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
60     @VisibleForTesting
61     final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
62 
63     @NonNull
64     private final TaskFragmentCallback mCallback;
65 
66     @VisibleForTesting
67     @Nullable
68     TaskFragmentAnimationController mAnimationController;
69 
70     /**
71      * Callback that notifies the controller about changes to task fragments.
72      */
73     interface TaskFragmentCallback {
onTransactionReady(@onNull TaskFragmentTransaction transaction)74         void onTransactionReady(@NonNull TaskFragmentTransaction transaction);
75     }
76 
77     /**
78      * @param executor  callbacks from WM Core are posted on this executor. It should be tied to the
79      *                  UI thread that all other calls into methods of this class are also on.
80      */
JetpackTaskFragmentOrganizer(@onNull Executor executor, @NonNull TaskFragmentCallback callback)81     JetpackTaskFragmentOrganizer(@NonNull Executor executor,
82             @NonNull TaskFragmentCallback callback) {
83         super(executor);
84         mCallback = callback;
85     }
86 
87     @Override
unregisterOrganizer()88     public void unregisterOrganizer() {
89         if (mAnimationController != null) {
90             mAnimationController.unregisterRemoteAnimations();
91             mAnimationController = null;
92         }
93         super.unregisterOrganizer();
94     }
95 
96     /**
97      * Overrides the animation for transitions of embedded activities organized by this organizer.
98      */
overrideSplitAnimation()99     void overrideSplitAnimation() {
100         if (mAnimationController == null) {
101             mAnimationController = new TaskFragmentAnimationController(this);
102         }
103         mAnimationController.registerRemoteAnimations();
104     }
105 
106     /**
107      * Starts a new Activity and puts it into split with an existing Activity side-by-side.
108      * @param launchingFragmentToken    token for the launching TaskFragment. If it exists, it will
109      *                                  be resized based on {@param launchingFragmentBounds}.
110      *                                  Otherwise, we will create a new TaskFragment with the given
111      *                                  token for the {@param launchingActivity}.
112      * @param launchingFragmentBounds   the initial bounds for the launching TaskFragment.
113      * @param launchingActivity the Activity to put on the left hand side of the split as the
114      *                          primary.
115      * @param secondaryFragmentToken    token to create the secondary TaskFragment with.
116      * @param secondaryFragmentBounds   the initial bounds for the secondary TaskFragment
117      * @param activityIntent    Intent to start the secondary Activity with.
118      * @param activityOptions   ActivityOptions to start the secondary Activity with.
119      * @param windowingMode     the windowing mode to set for the TaskFragments.
120      * @param splitAttributes   the {@link SplitAttributes} to represent the split.
121      */
startActivityToSide(@onNull WindowContainerTransaction wct, @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds, @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken, @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes)122     void startActivityToSide(@NonNull WindowContainerTransaction wct,
123             @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
124             @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
125             @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
126             @Nullable Bundle activityOptions, @NonNull SplitRule rule,
127             @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) {
128         final IBinder ownerToken = launchingActivity.getActivityToken();
129 
130         // Create or resize the launching TaskFragment.
131         if (mFragmentInfos.containsKey(launchingFragmentToken)) {
132             resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
133             updateWindowingMode(wct, launchingFragmentToken, windowingMode);
134         } else {
135             createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
136                     launchingFragmentBounds, windowingMode, launchingActivity);
137         }
138         updateAnimationParams(wct, launchingFragmentToken, splitAttributes);
139 
140         // Create a TaskFragment for the secondary activity.
141         final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
142                 getOrganizerToken(), secondaryFragmentToken, ownerToken)
143                 .setInitialBounds(secondaryFragmentBounds)
144                 .setWindowingMode(windowingMode)
145                 // Make sure to set the paired fragment token so that the new TaskFragment will be
146                 // positioned right above the paired TaskFragment.
147                 // This is needed in case we need to launch a placeholder Activity to split below a
148                 // transparent always-expand Activity.
149                 .setPairedPrimaryFragmentToken(launchingFragmentToken)
150                 .build();
151         createTaskFragment(wct, fragmentOptions);
152         updateAnimationParams(wct, secondaryFragmentToken, splitAttributes);
153         wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
154                 activityOptions);
155 
156         // Set adjacent to each other so that the containers below will be invisible.
157         setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
158         setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
159                 false /* isStacked */);
160     }
161 
162     /**
163      * Expands an existing TaskFragment to fill parent.
164      * @param wct WindowContainerTransaction in which the task fragment should be resized.
165      * @param fragmentToken token of an existing TaskFragment.
166      */
expandTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken)167     void expandTaskFragment(@NonNull WindowContainerTransaction wct,
168             @NonNull IBinder fragmentToken) {
169         resizeTaskFragment(wct, fragmentToken, new Rect());
170         setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
171         updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
172         updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
173     }
174 
175     /**
176      * Expands an Activity to fill parent by moving it to a new TaskFragment.
177      * @param fragmentToken token to create new TaskFragment with.
178      * @param activity      activity to move to the fill-parent TaskFragment.
179      */
expandActivity(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull Activity activity)180     void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
181             @NonNull Activity activity) {
182         createTaskFragmentAndReparentActivity(
183                 wct, fragmentToken, activity.getActivityToken(), new Rect(),
184                 WINDOWING_MODE_UNDEFINED, activity);
185         updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
186     }
187 
188     /**
189      * @param ownerToken The token of the activity that creates this task fragment. It does not
190      *                   have to be a child of this task fragment, but must belong to the same task.
191      */
createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode)192     void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
193             @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
194         createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
195                 null /* pairedActivityToken */);
196     }
197 
198     /**
199      * @param ownerToken The token of the activity that creates this task fragment. It does not
200      *                   have to be a child of this task fragment, but must belong to the same task.
201      * @param pairedActivityToken The token of the activity that will be reparented to this task
202      *                            fragment. When it is not {@code null}, the task fragment will be
203      *                            positioned right above it.
204      */
createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, @Nullable IBinder pairedActivityToken)205     void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
206             @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
207             @Nullable IBinder pairedActivityToken) {
208         final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
209                 getOrganizerToken(), fragmentToken, ownerToken)
210                 .setInitialBounds(bounds)
211                 .setWindowingMode(windowingMode)
212                 .setPairedActivityToken(pairedActivityToken)
213                 .build();
214         createTaskFragment(wct, fragmentOptions);
215     }
216 
createTaskFragment(@onNull WindowContainerTransaction wct, @NonNull TaskFragmentCreationParams fragmentOptions)217     void createTaskFragment(@NonNull WindowContainerTransaction wct,
218             @NonNull TaskFragmentCreationParams fragmentOptions) {
219         if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
220             throw new IllegalArgumentException(
221                     "There is an existing TaskFragment with fragmentToken="
222                             + fragmentOptions.getFragmentToken());
223         }
224         wct.createTaskFragment(fragmentOptions);
225     }
226 
227     /**
228      * @param ownerToken The token of the activity that creates this task fragment. It does not
229      *                   have to be a child of this task fragment, but must belong to the same task.
230      */
createTaskFragmentAndReparentActivity(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, @NonNull Activity activity)231     private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
232             @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
233             @WindowingMode int windowingMode, @NonNull Activity activity) {
234         final IBinder reparentActivityToken = activity.getActivityToken();
235         createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
236                 reparentActivityToken);
237         wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
238     }
239 
setAdjacentTaskFragments(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule)240     void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
241             @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
242         WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
243         final boolean finishSecondaryWithPrimary =
244                 splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
245         final boolean finishPrimaryWithSecondary =
246                 splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
247         if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
248             adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
249             adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
250             adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
251         }
252         wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
253     }
254 
setCompanionTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule, boolean isStacked)255     void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
256             @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
257             boolean isStacked) {
258         final boolean finishPrimaryWithSecondary;
259         if (isStacked) {
260             finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
261                     getFinishPrimaryWithSecondaryBehavior(splitRule));
262         } else {
263             finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
264         }
265         wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
266 
267         final boolean finishSecondaryWithPrimary;
268         if (isStacked) {
269             finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
270                     getFinishSecondaryWithPrimaryBehavior(splitRule));
271         } else {
272             finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
273         }
274         wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
275     }
276 
resizeTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @Nullable Rect bounds)277     void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
278             @Nullable Rect bounds) {
279         if (!mFragmentInfos.containsKey(fragmentToken)) {
280             throw new IllegalArgumentException(
281                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
282         }
283         if (bounds == null) {
284             bounds = new Rect();
285         }
286         wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
287     }
288 
updateWindowingMode(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @WindowingMode int windowingMode)289     void updateWindowingMode(@NonNull WindowContainerTransaction wct,
290             @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
291         if (!mFragmentInfos.containsKey(fragmentToken)) {
292             throw new IllegalArgumentException(
293                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
294         }
295         wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
296     }
297 
298     /**
299      * Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on
300      * {@link SplitAttributes}.
301      */
updateAnimationParams(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes)302     void updateAnimationParams(@NonNull WindowContainerTransaction wct,
303             @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) {
304         updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes));
305     }
306 
updateAnimationParams(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams)307     void updateAnimationParams(@NonNull WindowContainerTransaction wct,
308             @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
309         final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
310                 OP_TYPE_SET_ANIMATION_PARAMS)
311                 .setAnimationParams(animationParams)
312                 .build();
313         wct.setTaskFragmentOperation(fragmentToken, operation);
314     }
315 
deleteTaskFragment(@onNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken)316     void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
317             @NonNull IBinder fragmentToken) {
318         if (!mFragmentInfos.containsKey(fragmentToken)) {
319             throw new IllegalArgumentException(
320                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
321         }
322         wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
323     }
324 
updateTaskFragmentInfo(@onNull TaskFragmentInfo taskFragmentInfo)325     void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
326         mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
327     }
328 
removeTaskFragmentInfo(@onNull TaskFragmentInfo taskFragmentInfo)329     void removeTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
330         mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
331     }
332 
333     @Override
onTransactionReady(@onNull TaskFragmentTransaction transaction)334     public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
335         mCallback.onTransactionReady(transaction);
336     }
337 
createAnimationParamsOrDefault( @ullable SplitAttributes splitAttributes)338     private static TaskFragmentAnimationParams createAnimationParamsOrDefault(
339             @Nullable SplitAttributes splitAttributes) {
340         if (splitAttributes == null) {
341             return TaskFragmentAnimationParams.DEFAULT;
342         }
343         return new TaskFragmentAnimationParams.Builder()
344                 .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
345                 .build();
346     }
347 }
348