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