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