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_MULTI_WINDOW;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 
22 import android.app.Activity;
23 import android.app.WindowConfiguration.WindowingMode;
24 import android.content.Intent;
25 import android.content.res.Configuration;
26 import android.graphics.Rect;
27 import android.os.Bundle;
28 import android.os.IBinder;
29 import android.util.ArrayMap;
30 import android.window.TaskFragmentCreationParams;
31 import android.window.TaskFragmentInfo;
32 import android.window.TaskFragmentOrganizer;
33 import android.window.WindowContainerTransaction;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 
38 import java.util.Map;
39 import java.util.concurrent.Executor;
40 
41 /**
42  * Platform default Extensions implementation of {@link TaskFragmentOrganizer} to organize
43  * task fragments.
44  *
45  * All calls into methods of this class are expected to be on the UI thread.
46  */
47 class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
48 
49     /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
50     private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
51 
52     /**
53      * Mapping from the client assigned unique token to the TaskFragment parent
54      * {@link Configuration}.
55      */
56     final Map<IBinder, Configuration> mFragmentParentConfigs = new ArrayMap<>();
57 
58     private final TaskFragmentCallback mCallback;
59     private TaskFragmentAnimationController mAnimationController;
60 
61     /**
62      * Callback that notifies the controller about changes to task fragments.
63      */
64     interface TaskFragmentCallback {
onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)65         void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)66         void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)67         void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
onTaskFragmentParentInfoChanged(@onNull IBinder fragmentToken, @NonNull Configuration parentConfig)68         void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
69                 @NonNull Configuration parentConfig);
70     }
71 
72     /**
73      * @param executor  callbacks from WM Core are posted on this executor. It should be tied to the
74      *                  UI thread that all other calls into methods of this class are also on.
75      */
JetpackTaskFragmentOrganizer(@onNull Executor executor, TaskFragmentCallback callback)76     JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
77         super(executor);
78         mCallback = callback;
79     }
80 
81     @Override
unregisterOrganizer()82     public void unregisterOrganizer() {
83         stopOverrideSplitAnimation();
84         mAnimationController = null;
85         super.unregisterOrganizer();
86     }
87 
startOverrideSplitAnimation()88     void startOverrideSplitAnimation() {
89         if (mAnimationController == null) {
90             mAnimationController = new TaskFragmentAnimationController(this);
91         }
92         mAnimationController.registerRemoteAnimations();
93     }
94 
stopOverrideSplitAnimation()95     void stopOverrideSplitAnimation() {
96         if (mAnimationController != null) {
97             mAnimationController.unregisterRemoteAnimations();
98         }
99     }
100 
101     /**
102      * Starts a new Activity and puts it into split with an existing Activity side-by-side.
103      * @param launchingFragmentToken    token for the launching TaskFragment. If it exists, it will
104      *                                  be resized based on {@param launchingFragmentBounds}.
105      *                                  Otherwise, we will create a new TaskFragment with the given
106      *                                  token for the {@param launchingActivity}.
107      * @param launchingFragmentBounds   the initial bounds for the launching TaskFragment.
108      * @param launchingActivity the Activity to put on the left hand side of the split as the
109      *                          primary.
110      * @param secondaryFragmentToken    token to create the secondary TaskFragment with.
111      * @param secondaryFragmentBounds   the initial bounds for the secondary TaskFragment
112      * @param activityIntent    Intent to start the secondary Activity with.
113      * @param activityOptions   ActivityOptions to start the secondary Activity with.
114      */
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)115     void startActivityToSide(@NonNull WindowContainerTransaction wct,
116             @NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
117             @NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
118             @NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
119             @Nullable Bundle activityOptions, @NonNull SplitRule rule) {
120         final IBinder ownerToken = launchingActivity.getActivityToken();
121 
122         // Create or resize the launching TaskFragment.
123         if (mFragmentInfos.containsKey(launchingFragmentToken)) {
124             resizeTaskFragment(wct, launchingFragmentToken, launchingFragmentBounds);
125         } else {
126             createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
127                     launchingFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, launchingActivity);
128         }
129 
130         // Create a TaskFragment for the secondary activity.
131         createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
132                 secondaryFragmentBounds, WINDOWING_MODE_MULTI_WINDOW, activityIntent,
133                 activityOptions);
134 
135         // Set adjacent to each other so that the containers below will be invisible.
136         setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
137     }
138 
139     /**
140      * Expands an existing TaskFragment to fill parent.
141      * @param wct WindowContainerTransaction in which the task fragment should be resized.
142      * @param fragmentToken token of an existing TaskFragment.
143      */
expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken)144     void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
145         resizeTaskFragment(wct, fragmentToken, new Rect());
146         setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
147     }
148 
149     /**
150      * Expands an existing TaskFragment to fill parent.
151      * @param fragmentToken token of an existing TaskFragment.
152      */
expandTaskFragment(IBinder fragmentToken)153     void expandTaskFragment(IBinder fragmentToken) {
154         WindowContainerTransaction wct = new WindowContainerTransaction();
155         expandTaskFragment(wct, fragmentToken);
156         applyTransaction(wct);
157     }
158 
159     /**
160      * Expands an Activity to fill parent by moving it to a new TaskFragment.
161      * @param fragmentToken token to create new TaskFragment with.
162      * @param activity      activity to move to the fill-parent TaskFragment.
163      */
expandActivity(IBinder fragmentToken, Activity activity)164     void expandActivity(IBinder fragmentToken, Activity activity) {
165         final WindowContainerTransaction wct = new WindowContainerTransaction();
166         createTaskFragmentAndReparentActivity(
167                 wct, fragmentToken, activity.getActivityToken(), new Rect(),
168                 WINDOWING_MODE_UNDEFINED, activity);
169         applyTransaction(wct);
170     }
171 
172     /**
173      * @param ownerToken The token of the activity that creates this task fragment. It does not
174      *                   have to be a child of this task fragment, but must belong to the same task.
175      */
createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode)176     void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
177             IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
178         final TaskFragmentCreationParams fragmentOptions =
179                 createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
180         wct.createTaskFragment(fragmentOptions);
181     }
182 
183     /**
184      * @param ownerToken The token of the activity that creates this task fragment. It does not
185      *                   have to be a child of this task fragment, but must belong to the same task.
186      */
createTaskFragmentAndReparentActivity( WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity)187     private void createTaskFragmentAndReparentActivity(
188             WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
189             @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
190         createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
191         wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
192     }
193 
194     /**
195      * @param ownerToken The token of the activity that creates this task fragment. It does not
196      *                   have to be a child of this task fragment, but must belong to the same task.
197      */
createTaskFragmentAndStartActivity( WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent, @Nullable Bundle activityOptions)198     private void createTaskFragmentAndStartActivity(
199             WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
200             @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
201             @Nullable Bundle activityOptions) {
202         createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
203         wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
204     }
205 
setAdjacentTaskFragments(@onNull WindowContainerTransaction wct, @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule)206     void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
207             @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
208         WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
209         final boolean finishSecondaryWithPrimary =
210                 splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule);
211         final boolean finishPrimaryWithSecondary =
212                 splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule);
213         if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) {
214             adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams();
215             adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary);
216             adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary);
217         }
218         wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
219     }
220 
createFragmentOptions(IBinder fragmentToken, IBinder ownerToken, Rect bounds, @WindowingMode int windowingMode)221     TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
222             Rect bounds, @WindowingMode int windowingMode) {
223         if (mFragmentInfos.containsKey(fragmentToken)) {
224             throw new IllegalArgumentException(
225                     "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
226         }
227 
228         return new TaskFragmentCreationParams.Builder(
229                 getOrganizerToken(),
230                 fragmentToken,
231                 ownerToken)
232                 .setInitialBounds(bounds)
233                 .setWindowingMode(windowingMode)
234                 .build();
235     }
236 
resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken, @Nullable Rect bounds)237     void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
238             @Nullable Rect bounds) {
239         if (!mFragmentInfos.containsKey(fragmentToken)) {
240             throw new IllegalArgumentException(
241                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
242         }
243         if (bounds == null) {
244             bounds = new Rect();
245         }
246         wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
247     }
248 
deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken)249     void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
250         if (!mFragmentInfos.containsKey(fragmentToken)) {
251             throw new IllegalArgumentException(
252                     "Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
253         }
254         wct.deleteTaskFragment(mFragmentInfos.get(fragmentToken).getToken());
255     }
256 
257     @Override
onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)258     public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
259         final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
260         mFragmentInfos.put(fragmentToken, taskFragmentInfo);
261 
262         if (mCallback != null) {
263             mCallback.onTaskFragmentAppeared(taskFragmentInfo);
264         }
265     }
266 
267     @Override
onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)268     public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
269         final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
270         mFragmentInfos.put(fragmentToken, taskFragmentInfo);
271 
272         if (mCallback != null) {
273             mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
274         }
275     }
276 
277     @Override
onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)278     public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
279         mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
280         mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken());
281 
282         if (mCallback != null) {
283             mCallback.onTaskFragmentVanished(taskFragmentInfo);
284         }
285     }
286 
287     @Override
onTaskFragmentParentInfoChanged( @onNull IBinder fragmentToken, @NonNull Configuration parentConfig)288     public void onTaskFragmentParentInfoChanged(
289             @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
290         mFragmentParentConfigs.put(fragmentToken, parentConfig);
291 
292         if (mCallback != null) {
293             mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig);
294         }
295     }
296 }
297