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 
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import static org.junit.Assert.fail;
25 
26 import android.app.Activity;
27 import android.content.res.Configuration;
28 import android.graphics.Rect;
29 import android.os.Binder;
30 import android.os.IBinder;
31 import android.server.wm.WindowManagerState.WindowContainer;
32 import android.util.ArrayMap;
33 import android.window.TaskFragmentCreationParams;
34 import android.window.TaskFragmentInfo;
35 import android.window.TaskFragmentOrganizer;
36 import android.window.WindowContainerTransaction;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 
44 import java.util.Map;
45 import java.util.concurrent.CountDownLatch;
46 import java.util.concurrent.TimeUnit;
47 import java.util.function.Predicate;
48 
49 public class TaskFragmentOrganizerTestBase extends WindowManagerTestBase {
50     public BasicTaskFragmentOrganizer mTaskFragmentOrganizer;
51 
52     @Before
53     @Override
setUp()54     public void setUp() throws Exception {
55         super.setUp();
56         mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer();
57         mTaskFragmentOrganizer.registerOrganizer();
58     }
59 
60     @After
tearDown()61     public void tearDown() {
62         mTaskFragmentOrganizer.unregisterOrganizer();
63     }
64 
getActivityToken(@onNull Activity activity)65     public static IBinder getActivityToken(@NonNull Activity activity) {
66         return activity.getWindow().getAttributes().token;
67     }
68 
assertEmptyTaskFragment(TaskFragmentInfo info, IBinder expectedTaskFragToken)69     public static void assertEmptyTaskFragment(TaskFragmentInfo info,
70             IBinder expectedTaskFragToken) {
71         assertTaskFragmentInfoValidity(info, expectedTaskFragToken);
72         assertWithMessage("TaskFragment must be empty").that(info.isEmpty()).isTrue();
73         assertWithMessage("TaskFragmentInfo#getActivities must be empty")
74                 .that(info.getActivities()).isEmpty();
75         assertWithMessage("TaskFragment must not contain any running Activity")
76                 .that(info.hasRunningActivity()).isFalse();
77         assertWithMessage("TaskFragment must not be visible").that(info.isVisible()).isFalse();
78     }
79 
assertNotEmptyTaskFragment(TaskFragmentInfo info, IBinder expectedTaskFragToken, @Nullable IBinder ... expectedActivityTokens)80     public static void assertNotEmptyTaskFragment(TaskFragmentInfo info,
81             IBinder expectedTaskFragToken, @Nullable IBinder ... expectedActivityTokens) {
82         assertTaskFragmentInfoValidity(info, expectedTaskFragToken);
83         assertWithMessage("TaskFragment must not be empty").that(info.isEmpty()).isFalse();
84         assertWithMessage("TaskFragment must contain running Activity")
85                 .that(info.hasRunningActivity()).isTrue();
86         if (expectedActivityTokens != null) {
87             assertWithMessage("TaskFragmentInfo#getActivities must be empty")
88                     .that(info.getActivities()).containsAtLeastElementsIn(expectedActivityTokens);
89         }
90     }
91 
assertTaskFragmentInfoValidity(TaskFragmentInfo info, IBinder expectedTaskFragToken)92     private static void assertTaskFragmentInfoValidity(TaskFragmentInfo info,
93             IBinder expectedTaskFragToken) {
94         assertWithMessage("TaskFragmentToken must match the token from "
95                 + "TaskFragmentCreationParams#getFragmentToken")
96                 .that(info.getFragmentToken()).isEqualTo(expectedTaskFragToken);
97         assertWithMessage("WindowContainerToken must not be null")
98                 .that(info.getToken()).isNotNull();
99         assertWithMessage("TaskFragmentInfo#getPositionInParent must not be null")
100                 .that(info.getPositionInParent()).isNotNull();
101         assertWithMessage("Configuration must not be empty")
102                 .that(info.getConfiguration()).isNotEqualTo(new Configuration());
103     }
104 
105     /**
106      * Verifies whether the window hierarchy is as expected or not.
107      * <p>
108      * The sample usage is as follows:
109      * <pre class="prettyprint">
110      * assertWindowHierarchy(rootTask, leafTask, taskFragment, activity);
111      * </pre></p>
112      *
113      * @param containers The containers to be verified. It should be put from top to down
114      */
assertWindowHierarchy(WindowContainer... containers)115     public static void assertWindowHierarchy(WindowContainer... containers) {
116         for (int i = 0; i < containers.length - 2; i++) {
117             final WindowContainer parent = containers[i];
118             final WindowContainer child = containers[i + 1];
119             assertWithMessage(parent + " must contains " + child)
120                     .that(parent.mChildren).contains(child);
121         }
122     }
123 
124     public static class BasicTaskFragmentOrganizer extends TaskFragmentOrganizer {
125         private final static int WAIT_TIMEOUT_IN_SECOND = 10;
126 
127         private final Map<IBinder, TaskFragmentInfo> mInfos = new ArrayMap<>();
128         private final Map<IBinder, TaskFragmentInfo> mRemovedInfos = new ArrayMap<>();
129         private IBinder mTaskFragToken;
130         private Configuration mParentConfig;
131         private IBinder mErrorToken;
132         private Throwable mThrowable;
133 
134         private CountDownLatch mAppearedLatch = new CountDownLatch(1);
135         private CountDownLatch mChangedLatch = new CountDownLatch(1);
136         private CountDownLatch mVanishedLatch = new CountDownLatch(1);
137         private CountDownLatch mParentChangedLatch = new CountDownLatch(1);
138         private CountDownLatch mErrorLatch = new CountDownLatch(1);
139 
BasicTaskFragmentOrganizer()140         BasicTaskFragmentOrganizer() {
141             super(Runnable::run);
142         }
143 
getTaskFragmentInfo(IBinder taskFragToken)144         public TaskFragmentInfo getTaskFragmentInfo(IBinder taskFragToken) {
145             return mInfos.get(taskFragToken);
146         }
147 
getRemovedTaskFragmentInfo(IBinder taskFragToken)148         public TaskFragmentInfo getRemovedTaskFragmentInfo(IBinder taskFragToken) {
149             return mRemovedInfos.get(taskFragToken);
150         }
151 
getThrowable()152         public Throwable getThrowable() {
153             return mThrowable;
154         }
155 
getErrorCallbackToken()156         public IBinder getErrorCallbackToken() {
157             return mErrorToken;
158         }
159 
resetLatch()160         public void resetLatch() {
161             mAppearedLatch = new CountDownLatch(1);
162             mChangedLatch = new CountDownLatch(1);
163             mVanishedLatch = new CountDownLatch(1);
164             mParentChangedLatch = new CountDownLatch(1);
165             mErrorLatch = new CountDownLatch(1);
166         }
167 
168         /**
169          * Generates a {@link TaskFragmentCreationParams} with {@code ownerToken} specified.
170          *
171          * @param ownerToken The token of {@link Activity} to create a TaskFragment under its parent
172          *                   Task
173          * @return the generated {@link TaskFragmentCreationParams}
174          */
175         @NonNull
generateTaskFragParams(@onNull IBinder ownerToken)176         public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken) {
177             return generateTaskFragParams(ownerToken, new Rect(), WINDOWING_MODE_UNDEFINED);
178         }
179 
180         @NonNull
generateTaskFragParams(@onNull IBinder ownerToken, @NonNull Rect bounds, int windowingMode)181         public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken,
182                 @NonNull Rect bounds, int windowingMode) {
183             return new TaskFragmentCreationParams.Builder(getOrganizerToken(), new Binder(),
184                     ownerToken)
185                     .setInitialBounds(bounds)
186                     .setWindowingMode(windowingMode)
187                     .build();
188         }
189 
setAppearedCount(int count)190         public void setAppearedCount(int count) {
191             mAppearedLatch = new CountDownLatch(count);
192         }
193 
waitForAndGetTaskFragmentInfo(IBinder taskFragToken, Predicate<TaskFragmentInfo> condition, String message)194         public TaskFragmentInfo waitForAndGetTaskFragmentInfo(IBinder taskFragToken,
195                 Predicate<TaskFragmentInfo> condition, String message) {
196             final TaskFragmentInfo[] info = new TaskFragmentInfo[1];
197             waitForOrFail(message, () -> {
198                 info[0] = getTaskFragmentInfo(taskFragToken);
199                 return condition.test(info[0]);
200             });
201             return info[0];
202         }
203 
waitForTaskFragmentCreated()204         public void waitForTaskFragmentCreated() {
205             try {
206                 assertThat(mAppearedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
207             } catch (InterruptedException e) {
208                 fail("Assertion failed because of" + e);
209             }
210         }
211 
waitForTaskFragmentInfoChanged()212         public void waitForTaskFragmentInfoChanged() {
213             try {
214                 assertThat(mChangedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
215             } catch (InterruptedException e) {
216                 fail("Assertion failed because of" + e);
217             }
218         }
219 
waitForTaskFragmentRemoved()220         public void waitForTaskFragmentRemoved() {
221             try {
222                 assertThat(mVanishedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
223             } catch (InterruptedException e) {
224                 fail("Assertion failed because of" + e);
225             }
226         }
227 
waitForParentConfigChanged()228         public void waitForParentConfigChanged() {
229             try {
230                 assertThat(mParentChangedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS))
231                         .isTrue();
232             } catch (InterruptedException e) {
233                 fail("Assertion failed because of" + e);
234             }
235         }
236 
waitForTaskFragmentError()237         public void waitForTaskFragmentError() {
238             try {
239                 assertThat(mErrorLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue();
240             } catch (InterruptedException e) {
241                 fail("Assertion failed because of" + e);
242             }
243         }
244 
removeAllTaskFragments()245         private void removeAllTaskFragments() {
246             final WindowContainerTransaction wct = new WindowContainerTransaction();
247             for (TaskFragmentInfo info : mInfos.values()) {
248                 wct.deleteTaskFragment(info.getToken());
249             }
250             applyTransaction(wct);
251         }
252 
253         @Override
unregisterOrganizer()254         public void unregisterOrganizer() {
255             removeAllTaskFragments();
256             mRemovedInfos.clear();
257             super.unregisterOrganizer();
258         }
259 
260         @Override
onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)261         public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
262             super.onTaskFragmentAppeared(taskFragmentInfo);
263             mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
264             mAppearedLatch.countDown();
265         }
266 
267         @Override
onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)268         public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
269             super.onTaskFragmentInfoChanged(taskFragmentInfo);
270             mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
271             mChangedLatch.countDown();
272         }
273 
274         @Override
onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)275         public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
276             super.onTaskFragmentVanished(taskFragmentInfo);
277             mInfos.remove(taskFragmentInfo.getFragmentToken());
278             mRemovedInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
279             mVanishedLatch.countDown();
280         }
281 
282         @Override
onTaskFragmentParentInfoChanged(@onNull IBinder fragmentToken, @NonNull Configuration parentConfig)283         public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
284                 @NonNull Configuration parentConfig) {
285             super.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig);
286             mTaskFragToken = fragmentToken;
287             mParentConfig = parentConfig;
288             mParentChangedLatch.countDown();
289         }
290 
291         @Override
onTaskFragmentError(@onNull IBinder errorCallbackToken, @NonNull Throwable exception)292         public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
293                 @NonNull Throwable exception) {
294             super.onTaskFragmentError(errorCallbackToken, exception);
295             mErrorToken = errorCallbackToken;
296             mThrowable = exception;
297             mErrorLatch.countDown();
298         }
299     }
300 }
301