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