• 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 android.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
21 import static android.server.wm.WindowManagerState.STATE_RESUMED;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assert.fail;
27 import static org.junit.Assume.assumeTrue;
28 
29 import android.app.Activity;
30 import android.app.Instrumentation;
31 import android.content.ComponentName;
32 import android.content.Intent;
33 import android.content.res.Configuration;
34 import android.graphics.Rect;
35 import android.os.Binder;
36 import android.os.IBinder;
37 import android.server.wm.WindowContextTests.TestActivity;
38 import android.server.wm.WindowManagerState.WindowContainer;
39 import android.server.wm.jetpack.utils.ExtensionUtil;
40 import android.util.ArrayMap;
41 import android.window.TaskFragmentCreationParams;
42 import android.window.TaskFragmentInfo;
43 import android.window.TaskFragmentOrganizer;
44 import android.window.WindowContainerTransaction;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 import androidx.test.InstrumentationRegistry;
49 
50 import org.junit.After;
51 import org.junit.Before;
52 
53 import java.util.Map;
54 import java.util.concurrent.CountDownLatch;
55 import java.util.concurrent.TimeUnit;
56 import java.util.function.Predicate;
57 
58 public class TaskFragmentOrganizerTestBase extends WindowManagerTestBase {
59     public BasicTaskFragmentOrganizer mTaskFragmentOrganizer;
60     Activity mOwnerActivity;
61     IBinder mOwnerToken;
62     ComponentName mOwnerActivityName;
63     int mOwnerTaskId;
64 
65     @Before
66     @Override
setUp()67     public void setUp() throws Exception {
68         super.setUp();
69         mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer();
70         mTaskFragmentOrganizer.registerOrganizer();
71         mOwnerActivity = setUpOwnerActivity();
72         mOwnerToken = getActivityToken(mOwnerActivity);
73         mOwnerActivityName = mOwnerActivity.getComponentName();
74         mOwnerTaskId = mOwnerActivity.getTaskId();
75         // Make sure the activity is launched and resumed, otherwise the window state may not be
76         // stable.
77         waitAndAssertResumedActivity(mOwnerActivity.getComponentName(),
78                 "The owner activity must be resumed.");
79     }
80 
81     /** Setups the owner activity of the organized TaskFragment. */
setUpOwnerActivity()82     Activity setUpOwnerActivity() {
83         // Launch activities in fullscreen in case the device may use freeform as the default
84         // windowing mode.
85         return startActivityInWindowingModeFullScreen(TestActivity.class);
86     }
87 
88     @After
tearDown()89     public void tearDown() {
90         if (mTaskFragmentOrganizer != null) {
91             mTaskFragmentOrganizer.unregisterOrganizer();
92         }
93     }
94 
getActivityToken(@onNull Activity activity)95     public static IBinder getActivityToken(@NonNull Activity activity) {
96         return activity.getWindow().getAttributes().token;
97     }
98 
assertEmptyTaskFragment(TaskFragmentInfo info, IBinder expectedTaskFragToken)99     public static void assertEmptyTaskFragment(TaskFragmentInfo info,
100             IBinder expectedTaskFragToken) {
101         assertTaskFragmentInfoValidity(info, expectedTaskFragToken);
102         assertWithMessage("TaskFragment must be empty").that(info.isEmpty()).isTrue();
103         assertWithMessage("TaskFragmentInfo#getActivities must be empty")
104                 .that(info.getActivities()).isEmpty();
105         assertWithMessage("TaskFragment must not contain any running Activity")
106                 .that(info.hasRunningActivity()).isFalse();
107         assertWithMessage("TaskFragment must not be visible").that(info.isVisible()).isFalse();
108     }
109 
assertNotEmptyTaskFragment(TaskFragmentInfo info, IBinder expectedTaskFragToken, @Nullable IBinder ... expectedActivityTokens)110     public static void assertNotEmptyTaskFragment(TaskFragmentInfo info,
111             IBinder expectedTaskFragToken, @Nullable IBinder ... expectedActivityTokens) {
112         assertTaskFragmentInfoValidity(info, expectedTaskFragToken);
113         assertWithMessage("TaskFragment must not be empty").that(info.isEmpty()).isFalse();
114         assertWithMessage("TaskFragment must contain running Activity")
115                 .that(info.hasRunningActivity()).isTrue();
116         if (expectedActivityTokens != null) {
117             assertWithMessage("TaskFragmentInfo#getActivities must be empty")
118                     .that(info.getActivities()).containsAtLeastElementsIn(expectedActivityTokens);
119         }
120     }
121 
assertTaskFragmentInfoValidity(TaskFragmentInfo info, IBinder expectedTaskFragToken)122     private static void assertTaskFragmentInfoValidity(TaskFragmentInfo info,
123             IBinder expectedTaskFragToken) {
124         assertWithMessage("TaskFragmentToken must match the token from "
125                 + "TaskFragmentCreationParams#getFragmentToken")
126                 .that(info.getFragmentToken()).isEqualTo(expectedTaskFragToken);
127         assertWithMessage("WindowContainerToken must not be null")
128                 .that(info.getToken()).isNotNull();
129         assertWithMessage("TaskFragmentInfo#getPositionInParent must not be null")
130                 .that(info.getPositionInParent()).isNotNull();
131         assertWithMessage("Configuration must not be empty")
132                 .that(info.getConfiguration()).isNotEqualTo(new Configuration());
133     }
134 
135     /**
136      * Verifies whether the window hierarchy is as expected or not.
137      * <p>
138      * The sample usage is as follows:
139      * <pre class="prettyprint">
140      * assertWindowHierarchy(rootTask, leafTask, taskFragment, activity);
141      * </pre></p>
142      *
143      * @param containers The containers to be verified. It should be put from top to down
144      */
assertWindowHierarchy(WindowContainer... containers)145     public static void assertWindowHierarchy(WindowContainer... containers) {
146         for (int i = 0; i < containers.length - 2; i++) {
147             final WindowContainer parent = containers[i];
148             final WindowContainer child = containers[i + 1];
149             assertWithMessage(parent + " must contains " + child)
150                     .that(parent.mChildren).contains(child);
151         }
152     }
153 
154     /**
155      * Builds, runs and waits for completion of task fragment creation transaction.
156      * @param componentName name of the activity to launch in the TF, or {@code null} if none.
157      * @return token of the created task fragment.
158      */
createTaskFragment(@ullable ComponentName componentName)159     TaskFragmentInfo createTaskFragment(@Nullable ComponentName componentName) {
160         return createTaskFragment(componentName, new Rect());
161     }
162 
163     /**
164      * Same as {@link #createTaskFragment(ComponentName)}, but allows to specify the bounds for the
165      * new task fragment.
166      */
createTaskFragment(@ullable ComponentName componentName, @NonNull Rect bounds)167     TaskFragmentInfo createTaskFragment(@Nullable ComponentName componentName,
168             @NonNull Rect bounds) {
169         return createTaskFragment(componentName, bounds, new WindowContainerTransaction());
170     }
171 
172     /**
173      * Same as {@link #createTaskFragment(ComponentName, Rect)}, but allows to specify the
174      * {@link WindowContainerTransaction} to use.
175      */
createTaskFragment(@ullable ComponentName componentName, @NonNull Rect bounds, @NonNull WindowContainerTransaction wct)176     TaskFragmentInfo createTaskFragment(@Nullable ComponentName componentName,
177             @NonNull Rect bounds, @NonNull WindowContainerTransaction wct) {
178         final TaskFragmentCreationParams params = generateTaskFragCreationParams(bounds);
179         final IBinder taskFragToken = params.getFragmentToken();
180         wct.createTaskFragment(params);
181         if (componentName != null) {
182             wct.startActivityInTaskFragment(taskFragToken, mOwnerToken,
183                     new Intent().setComponent(componentName), null /* activityOptions */);
184         }
185         mTaskFragmentOrganizer.applyTransaction(wct);
186         mTaskFragmentOrganizer.waitForTaskFragmentCreated();
187 
188         if (componentName != null) {
189             mWmState.waitForActivityState(componentName, STATE_RESUMED);
190         }
191 
192         return mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken);
193     }
194 
195     @NonNull
generateTaskFragCreationParams()196     TaskFragmentCreationParams generateTaskFragCreationParams() {
197         return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken);
198     }
199 
200     @NonNull
generateTaskFragCreationParams(@onNull Rect bounds)201     TaskFragmentCreationParams generateTaskFragCreationParams(@NonNull Rect bounds) {
202         return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken, bounds,
203                 WINDOWING_MODE_UNDEFINED);
204     }
205 
startNewActivity()206     static Activity startNewActivity() {
207         return startNewActivity(TestActivity.class);
208     }
209 
startNewActivity(Class<?> className)210     static Activity startNewActivity(Class<?> className) {
211         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
212         final Intent intent = new Intent(instrumentation.getTargetContext(), className)
213                 .addFlags(FLAG_ACTIVITY_NEW_TASK);
214         return instrumentation.startActivitySync(intent);
215     }
216 
217     /** For API changes that are introduced together with WM Extensions version 2. */
assumeExtensionVersionAtLeast2()218     static void assumeExtensionVersionAtLeast2() {
219         // TODO(b/232476698) Remove in the next Android release.
220         assumeTrue(ExtensionUtil.getExtensionVersion().getMajor() >= 2);
221     }
222 
223     public static class BasicTaskFragmentOrganizer extends TaskFragmentOrganizer {
224         private final static int WAIT_TIMEOUT_IN_SECOND = 10;
225 
226         private final Map<IBinder, TaskFragmentInfo> mInfos = new ArrayMap<>();
227         private final Map<IBinder, TaskFragmentInfo> mRemovedInfos = new ArrayMap<>();
228         private IBinder mTaskFragToken;
229         private Configuration mParentConfig;
230         private IBinder mErrorToken;
231         private Throwable mThrowable;
232 
233         private CountDownLatch mAppearedLatch = new CountDownLatch(1);
234         private CountDownLatch mChangedLatch = new CountDownLatch(1);
235         private CountDownLatch mVanishedLatch = new CountDownLatch(1);
236         private CountDownLatch mParentChangedLatch = new CountDownLatch(1);
237         private CountDownLatch mErrorLatch = new CountDownLatch(1);
238 
BasicTaskFragmentOrganizer()239         BasicTaskFragmentOrganizer() {
240             super(Runnable::run);
241         }
242 
getTaskFragmentInfo(IBinder taskFragToken)243         public TaskFragmentInfo getTaskFragmentInfo(IBinder taskFragToken) {
244             return mInfos.get(taskFragToken);
245         }
246 
getRemovedTaskFragmentInfo(IBinder taskFragToken)247         public TaskFragmentInfo getRemovedTaskFragmentInfo(IBinder taskFragToken) {
248             return mRemovedInfos.get(taskFragToken);
249         }
250 
getThrowable()251         public Throwable getThrowable() {
252             return mThrowable;
253         }
254 
getErrorCallbackToken()255         public IBinder getErrorCallbackToken() {
256             return mErrorToken;
257         }
258 
resetLatch()259         public void resetLatch() {
260             mAppearedLatch = new CountDownLatch(1);
261             mChangedLatch = new CountDownLatch(1);
262             mVanishedLatch = new CountDownLatch(1);
263             mParentChangedLatch = new CountDownLatch(1);
264             mErrorLatch = new CountDownLatch(1);
265         }
266 
267         /**
268          * Generates a {@link TaskFragmentCreationParams} with {@code ownerToken} specified.
269          *
270          * @param ownerToken The token of {@link Activity} to create a TaskFragment under its parent
271          *                   Task
272          * @return the generated {@link TaskFragmentCreationParams}
273          */
274         @NonNull
generateTaskFragParams(@onNull IBinder ownerToken)275         public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken) {
276             return generateTaskFragParams(ownerToken, new Rect(), WINDOWING_MODE_UNDEFINED);
277         }
278 
279         @NonNull
generateTaskFragParams(@onNull IBinder ownerToken, @NonNull Rect bounds, int windowingMode)280         public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken,
281                 @NonNull Rect bounds, int windowingMode) {
282             return generateTaskFragParams(new Binder(), ownerToken, bounds, windowingMode);
283         }
284 
285         @NonNull
generateTaskFragParams(@onNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, int windowingMode)286         public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder fragmentToken,
287                 @NonNull IBinder ownerToken, @NonNull Rect bounds, int windowingMode) {
288             return new TaskFragmentCreationParams.Builder(getOrganizerToken(), fragmentToken,
289                     ownerToken)
290                     .setInitialBounds(bounds)
291                     .setWindowingMode(windowingMode)
292                     .build();
293         }
294 
setAppearedCount(int count)295         public void setAppearedCount(int count) {
296             mAppearedLatch = new CountDownLatch(count);
297         }
298 
waitForAndGetTaskFragmentInfo(IBinder taskFragToken, Predicate<TaskFragmentInfo> condition, String message)299         public TaskFragmentInfo waitForAndGetTaskFragmentInfo(IBinder taskFragToken,
300                 Predicate<TaskFragmentInfo> condition, String message) {
301             final TaskFragmentInfo[] info = new TaskFragmentInfo[1];
302             waitForOrFail(message, () -> {
303                 info[0] = getTaskFragmentInfo(taskFragToken);
304                 return condition.test(info[0]);
305             });
306             return info[0];
307         }
308 
waitForTaskFragmentCreated()309         public void waitForTaskFragmentCreated() {
310             try {
311                 assertThat(mAppearedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
312             } catch (InterruptedException e) {
313                 fail("Assertion failed because of" + e);
314             }
315         }
316 
waitForTaskFragmentInfoChanged()317         public void waitForTaskFragmentInfoChanged() {
318             try {
319                 assertThat(mChangedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
320             } catch (InterruptedException e) {
321                 fail("Assertion failed because of" + e);
322             }
323         }
324 
waitForTaskFragmentRemoved()325         public void waitForTaskFragmentRemoved() {
326             try {
327                 assertThat(mVanishedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
328             } catch (InterruptedException e) {
329                 fail("Assertion failed because of" + e);
330             }
331         }
332 
waitForParentConfigChanged()333         public void waitForParentConfigChanged() {
334             try {
335                 assertThat(mParentChangedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS))
336                         .isTrue();
337             } catch (InterruptedException e) {
338                 fail("Assertion failed because of" + e);
339             }
340         }
341 
waitForTaskFragmentError()342         public void waitForTaskFragmentError() {
343             try {
344                 assertThat(mErrorLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
345             } catch (InterruptedException e) {
346                 fail("Assertion failed because of" + e);
347             }
348         }
349 
removeAllTaskFragments()350         private void removeAllTaskFragments() {
351             final WindowContainerTransaction wct = new WindowContainerTransaction();
352             for (TaskFragmentInfo info : mInfos.values()) {
353                 wct.deleteTaskFragment(info.getToken());
354             }
355             applyTransaction(wct);
356         }
357 
358         @Override
unregisterOrganizer()359         public void unregisterOrganizer() {
360             removeAllTaskFragments();
361             mRemovedInfos.clear();
362             super.unregisterOrganizer();
363         }
364 
365         @Override
onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)366         public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
367             super.onTaskFragmentAppeared(taskFragmentInfo);
368             mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
369             mAppearedLatch.countDown();
370         }
371 
372         @Override
onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)373         public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
374             super.onTaskFragmentInfoChanged(taskFragmentInfo);
375             mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
376             mChangedLatch.countDown();
377         }
378 
379         @Override
onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)380         public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
381             super.onTaskFragmentVanished(taskFragmentInfo);
382             mInfos.remove(taskFragmentInfo.getFragmentToken());
383             mRemovedInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
384             mVanishedLatch.countDown();
385         }
386 
387         @Override
onTaskFragmentParentInfoChanged(@onNull IBinder fragmentToken, @NonNull Configuration parentConfig)388         public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
389                 @NonNull Configuration parentConfig) {
390             super.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig);
391             mTaskFragToken = fragmentToken;
392             mParentConfig = parentConfig;
393             mParentChangedLatch.countDown();
394         }
395 
396         @Override
onTaskFragmentError(@onNull IBinder errorCallbackToken, @NonNull Throwable exception)397         public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
398                 @NonNull Throwable exception) {
399             super.onTaskFragmentError(errorCallbackToken, exception);
400             mErrorToken = errorCallbackToken;
401             mThrowable = exception;
402             mErrorLatch.countDown();
403         }
404     }
405 }
406