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.ActivityTaskManager.INVALID_STACK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 22 import static android.server.wm.WindowManagerState.STATE_RESUMED; 23 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; 24 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; 25 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; 26 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; 27 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; 28 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; 29 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; 30 31 import static com.google.common.truth.Truth.assertThat; 32 import static com.google.common.truth.Truth.assertWithMessage; 33 34 import static org.junit.Assert.fail; 35 import static org.junit.Assume.assumeTrue; 36 37 import android.app.Activity; 38 import android.app.Instrumentation; 39 import android.content.ComponentName; 40 import android.content.Intent; 41 import android.content.res.Configuration; 42 import android.graphics.Rect; 43 import android.os.Binder; 44 import android.os.Bundle; 45 import android.os.IBinder; 46 import android.server.wm.WindowContextTests.TestActivity; 47 import android.server.wm.WindowManagerState.WindowContainer; 48 import android.util.ArrayMap; 49 import android.util.Log; 50 import android.window.TaskFragmentCreationParams; 51 import android.window.TaskFragmentInfo; 52 import android.window.TaskFragmentOrganizer; 53 import android.window.TaskFragmentTransaction; 54 import android.window.WindowContainerTransaction; 55 56 import androidx.annotation.NonNull; 57 import androidx.annotation.Nullable; 58 import androidx.test.InstrumentationRegistry; 59 60 import org.junit.After; 61 import org.junit.Before; 62 63 import java.util.List; 64 import java.util.Map; 65 import java.util.concurrent.CountDownLatch; 66 import java.util.concurrent.TimeUnit; 67 import java.util.function.Predicate; 68 69 import javax.annotation.concurrent.GuardedBy; 70 71 public class TaskFragmentOrganizerTestBase extends WindowManagerTestBase { 72 private static final String TAG = "TaskFragmentOrganizerTestBase"; 73 74 public BasicTaskFragmentOrganizer mTaskFragmentOrganizer; 75 Activity mOwnerActivity; 76 IBinder mOwnerToken; 77 ComponentName mOwnerActivityName; 78 int mOwnerTaskId; 79 80 @Before 81 @Override setUp()82 public void setUp() throws Exception { 83 super.setUp(); 84 assumeTrue(supportsMultiWindow()); 85 mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer(); 86 mTaskFragmentOrganizer.registerOrganizer(); 87 mOwnerActivity = setUpOwnerActivity(); 88 mOwnerToken = getActivityToken(mOwnerActivity); 89 mOwnerActivityName = mOwnerActivity.getComponentName(); 90 mOwnerTaskId = mOwnerActivity.getTaskId(); 91 // Make sure the activity is launched and resumed, otherwise the window state may not be 92 // stable. 93 waitAndAssertResumedActivity(mOwnerActivity.getComponentName(), 94 "The owner activity must be resumed."); 95 } 96 97 /** Setups the owner activity of the organized TaskFragment. */ setUpOwnerActivity()98 Activity setUpOwnerActivity() { 99 // Launch activities in fullscreen in case the device may use freeform as the default 100 // windowing mode. 101 return startActivityInWindowingModeFullScreen(TestActivity.class); 102 } 103 104 @After tearDown()105 public void tearDown() { 106 if (mTaskFragmentOrganizer != null) { 107 mTaskFragmentOrganizer.unregisterOrganizer(); 108 } 109 } 110 getActivityToken(@onNull Activity activity)111 public static IBinder getActivityToken(@NonNull Activity activity) { 112 return activity.getWindow().getAttributes().token; 113 } 114 assertEmptyTaskFragment(TaskFragmentInfo info, IBinder expectedTaskFragToken)115 public static void assertEmptyTaskFragment(TaskFragmentInfo info, 116 IBinder expectedTaskFragToken) { 117 assertTaskFragmentInfoValidity(info, expectedTaskFragToken); 118 assertWithMessage("TaskFragment must be empty").that(info.isEmpty()).isTrue(); 119 assertWithMessage("TaskFragmentInfo#getActivities must be empty") 120 .that(info.getActivities()).isEmpty(); 121 assertWithMessage("TaskFragment must not contain any running Activity") 122 .that(info.hasRunningActivity()).isFalse(); 123 assertWithMessage("TaskFragment must not be visible").that(info.isVisible()).isFalse(); 124 } 125 assertNotEmptyTaskFragment(TaskFragmentInfo info, IBinder expectedTaskFragToken, @Nullable IBinder ... expectedActivityTokens)126 public static void assertNotEmptyTaskFragment(TaskFragmentInfo info, 127 IBinder expectedTaskFragToken, @Nullable IBinder ... expectedActivityTokens) { 128 assertTaskFragmentInfoValidity(info, expectedTaskFragToken); 129 assertWithMessage("TaskFragment must not be empty").that(info.isEmpty()).isFalse(); 130 assertWithMessage("TaskFragment must contain running Activity") 131 .that(info.hasRunningActivity()).isTrue(); 132 if (expectedActivityTokens != null) { 133 assertWithMessage("TaskFragmentInfo#getActivities must be empty") 134 .that(info.getActivities()).containsAtLeastElementsIn(expectedActivityTokens); 135 } 136 } 137 assertTaskFragmentInfoValidity(TaskFragmentInfo info, IBinder expectedTaskFragToken)138 private static void assertTaskFragmentInfoValidity(TaskFragmentInfo info, 139 IBinder expectedTaskFragToken) { 140 assertWithMessage("TaskFragmentToken must match the token from " 141 + "TaskFragmentCreationParams#getFragmentToken") 142 .that(info.getFragmentToken()).isEqualTo(expectedTaskFragToken); 143 assertWithMessage("WindowContainerToken must not be null") 144 .that(info.getToken()).isNotNull(); 145 assertWithMessage("TaskFragmentInfo#getPositionInParent must not be null") 146 .that(info.getPositionInParent()).isNotNull(); 147 assertWithMessage("Configuration must not be empty") 148 .that(info.getConfiguration()).isNotEqualTo(new Configuration()); 149 } 150 151 /** 152 * Verifies whether the window hierarchy is as expected or not. 153 * <p> 154 * The sample usage is as follows: 155 * <pre class="prettyprint"> 156 * assertWindowHierarchy(rootTask, leafTask, taskFragment, activity); 157 * </pre></p> 158 * 159 * @param containers The containers to be verified. It should be put from top to down 160 */ assertWindowHierarchy(WindowContainer... containers)161 public static void assertWindowHierarchy(WindowContainer... containers) { 162 for (int i = 0; i < containers.length - 2; i++) { 163 final WindowContainer parent = containers[i]; 164 final WindowContainer child = containers[i + 1]; 165 assertWithMessage(parent + " must contains " + child) 166 .that(parent.mChildren).contains(child); 167 } 168 } 169 170 /** 171 * Builds, runs and waits for completion of task fragment creation transaction. 172 * @param componentName name of the activity to launch in the TF, or {@code null} if none. 173 * @return token of the created task fragment. 174 */ createTaskFragment(@ullable ComponentName componentName)175 TaskFragmentInfo createTaskFragment(@Nullable ComponentName componentName) { 176 return createTaskFragment(componentName, new Rect()); 177 } 178 179 /** 180 * Same as {@link #createTaskFragment(ComponentName)}, but allows to specify the bounds for the 181 * new task fragment. 182 */ createTaskFragment(@ullable ComponentName componentName, @NonNull Rect relativeBounds)183 TaskFragmentInfo createTaskFragment(@Nullable ComponentName componentName, 184 @NonNull Rect relativeBounds) { 185 return createTaskFragment(componentName, relativeBounds, new WindowContainerTransaction()); 186 } 187 188 /** 189 * Same as {@link #createTaskFragment(ComponentName, Rect)}, but allows to specify the 190 * {@link WindowContainerTransaction} to use. 191 */ createTaskFragment(@ullable ComponentName componentName, @NonNull Rect relativeBounds, @NonNull WindowContainerTransaction wct)192 TaskFragmentInfo createTaskFragment(@Nullable ComponentName componentName, 193 @NonNull Rect relativeBounds, @NonNull WindowContainerTransaction wct) { 194 final TaskFragmentCreationParams params = generateTaskFragCreationParams(relativeBounds); 195 final IBinder taskFragToken = params.getFragmentToken(); 196 wct.createTaskFragment(params); 197 if (componentName != null) { 198 wct.startActivityInTaskFragment(taskFragToken, mOwnerToken, 199 new Intent().setComponent(componentName), null /* activityOptions */); 200 } 201 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 202 false /* shouldApplyIndependently */); 203 mTaskFragmentOrganizer.waitForTaskFragmentCreated(); 204 205 if (componentName != null) { 206 mWmState.waitForActivityState(componentName, STATE_RESUMED); 207 } 208 209 return mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); 210 } 211 212 @NonNull generateTaskFragCreationParams()213 TaskFragmentCreationParams generateTaskFragCreationParams() { 214 return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken); 215 } 216 217 @NonNull generateTaskFragCreationParams(@onNull Rect relativeBounds)218 TaskFragmentCreationParams generateTaskFragCreationParams(@NonNull Rect relativeBounds) { 219 return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken, relativeBounds, 220 WINDOWING_MODE_UNDEFINED); 221 } 222 startNewActivity()223 static Activity startNewActivity() { 224 return startNewActivity(TestActivity.class); 225 } 226 startNewActivity(Class<?> className)227 static Activity startNewActivity(Class<?> className) { 228 final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 229 final Intent intent = new Intent(instrumentation.getTargetContext(), className) 230 .addFlags(FLAG_ACTIVITY_NEW_TASK); 231 return instrumentation.startActivitySync(intent); 232 } 233 234 public static class BasicTaskFragmentOrganizer extends TaskFragmentOrganizer { 235 private final static int WAIT_TIMEOUT_IN_SECOND = 10; 236 237 private final Object mLock = new Object(); 238 239 @GuardedBy("mLock") 240 private final Map<IBinder, TaskFragmentInfo> mInfos = new ArrayMap<>(); 241 @GuardedBy("mLock") 242 private final Map<IBinder, TaskFragmentInfo> mRemovedInfos = new ArrayMap<>(); 243 @GuardedBy("mLock") 244 private int mParentTaskId = INVALID_STACK_ID; 245 @Nullable 246 @GuardedBy("mLock") 247 private Configuration mParentConfig; 248 @Nullable 249 @GuardedBy("mLock") 250 private IBinder mErrorToken; 251 @Nullable 252 @GuardedBy("mLock") 253 private Throwable mThrowable; 254 @GuardedBy("mLock") 255 private boolean mIsRegistered; 256 257 private CountDownLatch mAppearedLatch = new CountDownLatch(1); 258 private CountDownLatch mChangedLatch = new CountDownLatch(1); 259 private CountDownLatch mVanishedLatch = new CountDownLatch(1); 260 private CountDownLatch mParentChangedLatch = new CountDownLatch(1); 261 private CountDownLatch mErrorLatch = new CountDownLatch(1); 262 BasicTaskFragmentOrganizer()263 BasicTaskFragmentOrganizer() { 264 super(Runnable::run); 265 } 266 getTaskFragmentInfo(IBinder taskFragToken)267 public TaskFragmentInfo getTaskFragmentInfo(IBinder taskFragToken) { 268 synchronized (mLock) { 269 return mInfos.get(taskFragToken); 270 } 271 } 272 getRemovedTaskFragmentInfo(IBinder taskFragToken)273 public TaskFragmentInfo getRemovedTaskFragmentInfo(IBinder taskFragToken) { 274 synchronized (mLock) { 275 return mRemovedInfos.get(taskFragToken); 276 } 277 } 278 getThrowable()279 public Throwable getThrowable() { 280 synchronized (mLock) { 281 return mThrowable; 282 } 283 } 284 getErrorCallbackToken()285 public IBinder getErrorCallbackToken() { 286 synchronized (mLock) { 287 return mErrorToken; 288 } 289 } 290 resetLatch()291 public void resetLatch() { 292 mAppearedLatch = new CountDownLatch(1); 293 mChangedLatch = new CountDownLatch(1); 294 mVanishedLatch = new CountDownLatch(1); 295 mParentChangedLatch = new CountDownLatch(1); 296 mErrorLatch = new CountDownLatch(1); 297 } 298 299 /** 300 * Generates a {@link TaskFragmentCreationParams} with {@code ownerToken} specified. 301 * 302 * @param ownerToken The token of {@link Activity} to create a TaskFragment under its parent 303 * Task 304 * @return the generated {@link TaskFragmentCreationParams} 305 */ 306 @NonNull generateTaskFragParams(@onNull IBinder ownerToken)307 public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken) { 308 return generateTaskFragParams(ownerToken, new Rect(), WINDOWING_MODE_UNDEFINED); 309 } 310 311 @NonNull generateTaskFragParams(@onNull IBinder ownerToken, @NonNull Rect relativeBounds, int windowingMode)312 public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken, 313 @NonNull Rect relativeBounds, int windowingMode) { 314 return generateTaskFragParams(new Binder(), ownerToken, relativeBounds, windowingMode); 315 } 316 317 @NonNull generateTaskFragParams(@onNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect relativeBounds, int windowingMode)318 public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder fragmentToken, 319 @NonNull IBinder ownerToken, @NonNull Rect relativeBounds, int windowingMode) { 320 return new TaskFragmentCreationParams.Builder(getOrganizerToken(), fragmentToken, 321 ownerToken) 322 .setInitialRelativeBounds(relativeBounds) 323 .setWindowingMode(windowingMode) 324 .build(); 325 } 326 setAppearedCount(int count)327 public void setAppearedCount(int count) { 328 mAppearedLatch = new CountDownLatch(count); 329 } 330 waitForAndGetTaskFragmentInfo(IBinder taskFragToken, Predicate<TaskFragmentInfo> condition, String message)331 public TaskFragmentInfo waitForAndGetTaskFragmentInfo(IBinder taskFragToken, 332 Predicate<TaskFragmentInfo> condition, String message) { 333 final TaskFragmentInfo[] info = new TaskFragmentInfo[1]; 334 waitForOrFail(message, () -> { 335 info[0] = getTaskFragmentInfo(taskFragToken); 336 return condition.test(info[0]); 337 }); 338 return info[0]; 339 } 340 waitForTaskFragmentCreated()341 public void waitForTaskFragmentCreated() { 342 try { 343 assertThat(mAppearedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue(); 344 } catch (InterruptedException e) { 345 fail("Assertion failed because of" + e); 346 } 347 } 348 waitForTaskFragmentInfoChanged()349 public void waitForTaskFragmentInfoChanged() { 350 try { 351 assertThat(mChangedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue(); 352 } catch (InterruptedException e) { 353 fail("Assertion failed because of" + e); 354 } 355 } 356 waitForTaskFragmentRemoved()357 public void waitForTaskFragmentRemoved() { 358 try { 359 assertThat(mVanishedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue(); 360 } catch (InterruptedException e) { 361 fail("Assertion failed because of" + e); 362 } 363 } 364 waitForParentConfigChanged()365 public void waitForParentConfigChanged() { 366 try { 367 assertThat(mParentChangedLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)) 368 .isTrue(); 369 } catch (InterruptedException e) { 370 fail("Assertion failed because of" + e); 371 } 372 } 373 waitForTaskFragmentError()374 public void waitForTaskFragmentError() { 375 try { 376 assertThat(mErrorLatch.await(WAIT_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)).isTrue(); 377 } catch (InterruptedException e) { 378 fail("Assertion failed because of" + e); 379 } 380 } 381 382 @GuardedBy("mLock") removeAllTaskFragments()383 private void removeAllTaskFragments() { 384 final WindowContainerTransaction wct = new WindowContainerTransaction(); 385 for (TaskFragmentInfo info : mInfos.values()) { 386 wct.deleteTaskFragment(info.getFragmentToken()); 387 } 388 applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, 389 false /* shouldApplyIndependently */); 390 } 391 392 @Override registerOrganizer()393 public void registerOrganizer() { 394 synchronized (mLock) { 395 mIsRegistered = true; 396 } 397 super.registerOrganizer(); 398 } 399 400 @Override unregisterOrganizer()401 public void unregisterOrganizer() { 402 synchronized (mLock) { 403 mIsRegistered = false; 404 removeAllTaskFragments(); 405 mRemovedInfos.clear(); 406 mInfos.clear(); 407 mParentTaskId = INVALID_STACK_ID; 408 mParentConfig = null; 409 mErrorToken = null; 410 mThrowable = null; 411 } 412 super.unregisterOrganizer(); 413 } 414 415 @Override onTransactionReady(@onNull TaskFragmentTransaction transaction)416 public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) { 417 synchronized (mLock) { 418 if (!mIsRegistered) { 419 // Ignore callback that is invoked after unregister. This can be a racing 420 // condition before the unregister reaches the server side. 421 return; 422 } 423 final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); 424 for (TaskFragmentTransaction.Change change : changes) { 425 final int taskId = change.getTaskId(); 426 final TaskFragmentInfo info = change.getTaskFragmentInfo(); 427 switch (change.getType()) { 428 case TYPE_TASK_FRAGMENT_APPEARED: 429 onTaskFragmentAppeared(info); 430 break; 431 case TYPE_TASK_FRAGMENT_INFO_CHANGED: 432 onTaskFragmentInfoChanged(info); 433 break; 434 case TYPE_TASK_FRAGMENT_VANISHED: 435 onTaskFragmentVanished(info); 436 break; 437 case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: 438 onTaskFragmentParentInfoChanged(taskId, change.getTaskConfiguration()); 439 break; 440 case TYPE_TASK_FRAGMENT_ERROR: 441 final Bundle errorBundle = change.getErrorBundle(); 442 final IBinder errorToken = change.getErrorCallbackToken(); 443 final TaskFragmentInfo errorTaskFragmentInfo = 444 errorBundle.getParcelable( 445 KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, 446 TaskFragmentInfo.class); 447 final int opType = errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE); 448 final Throwable exception = errorBundle.getSerializable( 449 KEY_ERROR_CALLBACK_THROWABLE, Throwable.class); 450 onTaskFragmentError(errorToken, errorTaskFragmentInfo, opType, 451 exception); 452 break; 453 case TYPE_ACTIVITY_REPARENTED_TO_TASK: 454 onActivityReparentedToTask( 455 taskId, 456 change.getActivityIntent(), 457 change.getActivityToken()); 458 break; 459 default: 460 // Log instead of throwing exception in case we will add more types 461 // between releases. 462 Log.w(TAG, "Unknown TaskFragmentEvent=" + change.getType()); 463 } 464 } 465 onTransactionHandled(transaction.getTransactionToken(), 466 new WindowContainerTransaction(), TASK_FRAGMENT_TRANSIT_NONE, 467 false /* shouldApplyIndependently */); 468 } 469 } 470 471 @GuardedBy("mLock") onTaskFragmentAppeared(@onNull TaskFragmentInfo taskFragmentInfo)472 private void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { 473 mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); 474 mAppearedLatch.countDown(); 475 } 476 477 @GuardedBy("mLock") onTaskFragmentInfoChanged(@onNull TaskFragmentInfo taskFragmentInfo)478 private void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { 479 mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); 480 mChangedLatch.countDown(); 481 } 482 483 @GuardedBy("mLock") onTaskFragmentVanished(@onNull TaskFragmentInfo taskFragmentInfo)484 private void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { 485 mInfos.remove(taskFragmentInfo.getFragmentToken()); 486 mRemovedInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); 487 mVanishedLatch.countDown(); 488 } 489 490 @GuardedBy("mLock") onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig)491 private void onTaskFragmentParentInfoChanged(int taskId, 492 @NonNull Configuration parentConfig) { 493 mParentTaskId = taskId; 494 mParentConfig = parentConfig; 495 mParentChangedLatch.countDown(); 496 } 497 498 @GuardedBy("mLock") onTaskFragmentError(@onNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception)499 private void onTaskFragmentError(@NonNull IBinder errorCallbackToken, 500 @Nullable TaskFragmentInfo taskFragmentInfo, int opType, 501 @NonNull Throwable exception) { 502 mErrorToken = errorCallbackToken; 503 if (taskFragmentInfo != null) { 504 mInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo); 505 } 506 mThrowable = exception; 507 mErrorLatch.countDown(); 508 } 509 onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken)510 private void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, 511 @NonNull IBinder activityToken) { 512 // TODO(b/232476698) Add CTS to verify PIP behavior with ActivityEmbedding 513 } 514 } 515 } 516