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 com.android.server.wm; 18 19 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 22 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 23 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; 24 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 25 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 26 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 27 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; 28 import static android.window.BackNavigationInfo.typeToString; 29 import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED; 30 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 35 import static com.android.server.wm.ActivityRecord.State.STOPPED; 36 37 import static com.google.common.truth.Truth.assertThat; 38 import static com.google.common.truth.Truth.assertWithMessage; 39 40 import static org.junit.Assert.assertEquals; 41 import static org.junit.Assert.assertFalse; 42 import static org.junit.Assert.assertTrue; 43 import static org.mockito.ArgumentMatchers.any; 44 import static org.mockito.ArgumentMatchers.anyInt; 45 import static org.mockito.ArgumentMatchers.eq; 46 import static org.mockito.Mockito.doAnswer; 47 import static org.mockito.Mockito.mock; 48 import static org.mockito.Mockito.never; 49 import static org.mockito.Mockito.when; 50 51 import android.annotation.NonNull; 52 import android.annotation.Nullable; 53 import android.app.ActivityOptions; 54 import android.content.Context; 55 import android.content.ContextWrapper; 56 import android.content.pm.ApplicationInfo; 57 import android.content.res.Resources; 58 import android.os.Bundle; 59 import android.os.Looper; 60 import android.os.RemoteCallback; 61 import android.os.RemoteException; 62 import android.platform.test.annotations.Presubmit; 63 import android.platform.test.annotations.RequiresFlagsDisabled; 64 import android.util.ArraySet; 65 import android.view.WindowManager; 66 import android.window.BackAnimationAdapter; 67 import android.window.BackMotionEvent; 68 import android.window.BackNavigationInfo; 69 import android.window.IBackAnimationHandoffHandler; 70 import android.window.IOnBackInvokedCallback; 71 import android.window.OnBackInvokedCallback; 72 import android.window.OnBackInvokedCallbackInfo; 73 import android.window.OnBackInvokedDispatcher; 74 import android.window.TaskFragmentOrganizer; 75 import android.window.TaskSnapshot; 76 import android.window.WindowOnBackInvokedDispatcher; 77 78 import com.android.server.LocalServices; 79 import com.android.window.flags.Flags; 80 81 import org.junit.Before; 82 import org.junit.Test; 83 import org.junit.runner.RunWith; 84 import org.mockito.Mockito; 85 import org.mockito.MockitoSession; 86 import org.mockito.quality.Strictness; 87 88 import java.util.ArrayList; 89 import java.util.concurrent.CountDownLatch; 90 import java.util.concurrent.TimeUnit; 91 92 /** 93 * Tests for the {@link BackNavigationController} class. 94 * 95 * Build/Install/Run: 96 * atest WmTests:BackNavigationControllerTests 97 */ 98 @Presubmit 99 @RunWith(WindowTestRunner.class) 100 public class BackNavigationControllerTests extends WindowTestsBase { 101 private BackNavigationController mBackNavigationController; 102 private WindowManagerInternal mWindowManagerInternal; 103 private BackAnimationAdapter mBackAnimationAdapter; 104 private BackNavigationController.NavigationMonitor mNavigationMonitor; 105 private Task mRootHomeTask; 106 107 @Before setUp()108 public void setUp() throws Exception { 109 final TransitionController transitionController = mAtm.getTransitionController(); 110 final Transition fakeTransition = new Transition(TRANSIT_PREPARE_BACK_NAVIGATION, 111 0 /* flag */, transitionController, transitionController.mSyncEngine); 112 spyOn(transitionController); 113 doReturn(fakeTransition).when(transitionController) 114 .createTransition(anyInt(), anyInt()); 115 116 final BackNavigationController original = new BackNavigationController(); 117 original.setWindowManager(mWm); 118 mBackNavigationController = Mockito.spy(original); 119 LocalServices.removeServiceForTest(WindowManagerInternal.class); 120 mWindowManagerInternal = mock(WindowManagerInternal.class); 121 LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); 122 mBackAnimationAdapter = mock(BackAnimationAdapter.class); 123 doReturn(true).when(mBackAnimationAdapter).isAnimatable(anyInt()); 124 Mockito.doNothing().when(mBackNavigationController).startAnimation(); 125 mNavigationMonitor = mock(BackNavigationController.NavigationMonitor.class); 126 mRootHomeTask = initHomeActivity(); 127 } 128 129 @Test backNavInfo_HomeWhenBackToLauncher()130 public void backNavInfo_HomeWhenBackToLauncher() { 131 Task task = createTopTaskWithActivity(); 132 IOnBackInvokedCallback callback = withSystemCallback(task); 133 134 BackNavigationInfo backNavigationInfo = startBackNavigation(); 135 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 136 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 137 assertThat(typeToString(backNavigationInfo.getType())) 138 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); 139 140 // verify if back animation would start. 141 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 142 } 143 144 @Test noBackWhenMoveTaskToBack()145 public void noBackWhenMoveTaskToBack() { 146 Task taskA = createTask(mDefaultDisplay); 147 ActivityRecord recordA = createActivityRecord(taskA); 148 Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any()); 149 150 final Task topTask = createTopTaskWithActivity(); 151 withSystemCallback(topTask); 152 // simulate moveTaskToBack 153 topTask.setVisibleRequested(false); 154 BackNavigationInfo backNavigationInfo = startBackNavigation(); 155 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNull(); 156 } 157 158 @Test backTypeCrossTaskWhenBackToPreviousTask()159 public void backTypeCrossTaskWhenBackToPreviousTask() { 160 Task taskA = createTask(mDefaultDisplay); 161 ActivityRecord recordA = createActivityRecord(taskA); 162 Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any()); 163 164 final Task topTask = createTopTaskWithActivity(); 165 withSystemCallback(topTask); 166 BackNavigationInfo backNavigationInfo = startBackNavigation(); 167 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 168 assertThat(typeToString(backNavigationInfo.getType())) 169 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK)); 170 171 // verify if back animation would start. 172 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 173 174 // reset drawing status to test translucent activity 175 backNavigationInfo.onBackNavigationFinished(false); 176 mBackNavigationController.clearBackAnimations(true); 177 final ActivityRecord topActivity = topTask.getTopMostActivity(); 178 makeWindowVisibleAndDrawn(topActivity.findMainWindow()); 179 // simulate translucent 180 topActivity.setOccludesParent(false); 181 backNavigationInfo = startBackNavigation(); 182 assertThat(typeToString(backNavigationInfo.getType())) 183 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 184 185 // reset drawing status to test if previous task is translucent activity 186 backNavigationInfo.onBackNavigationFinished(false); 187 mBackNavigationController.clearBackAnimations(true); 188 makeWindowVisibleAndDrawn(topActivity.findMainWindow()); 189 // simulate translucent 190 recordA.setOccludesParent(false); 191 backNavigationInfo = startBackNavigation(); 192 assertThat(typeToString(backNavigationInfo.getType())) 193 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 194 195 // reset drawing status to test keyguard occludes 196 topActivity.setOccludesParent(true); 197 recordA.setOccludesParent(true); 198 backNavigationInfo.onBackNavigationFinished(false); 199 mBackNavigationController.clearBackAnimations(true); 200 makeWindowVisibleAndDrawn(topActivity.findMainWindow()); 201 setupKeyguardOccluded(); 202 backNavigationInfo = startBackNavigation(); 203 assertThat(typeToString(backNavigationInfo.getType())) 204 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 205 206 backNavigationInfo.onBackNavigationFinished(false); 207 mBackNavigationController.clearBackAnimations(true); 208 doReturn(true).when(recordA).canShowWhenLocked(); 209 backNavigationInfo = startBackNavigation(); 210 assertThat(typeToString(backNavigationInfo.getType())) 211 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK)); 212 } 213 214 @Test backTypeBackToHomeDifferentUser()215 public void backTypeBackToHomeDifferentUser() { 216 Task taskA = createTask(mDefaultDisplay); 217 ActivityRecord recordA = createActivityRecord(taskA); 218 Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any()); 219 doReturn(false).when(taskA).showToCurrentUser(); 220 221 withSystemCallback(createTopTaskWithActivity()); 222 BackNavigationInfo backNavigationInfo = startBackNavigation(); 223 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 224 assertThat(typeToString(backNavigationInfo.getType())) 225 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); 226 } 227 228 @Test backTypeCrossActivityWithCustomizeExitAnimation()229 public void backTypeCrossActivityWithCustomizeExitAnimation() { 230 CrossActivityTestCase testCase = createTopTaskWithTwoActivities(); 231 IOnBackInvokedCallback callback = withSystemCallback(testCase.task); 232 testCase.windowFront.mAttrs.windowAnimations = 0x10; 233 spyOn(mDisplayContent.mAppTransition.mTransitionAnimation); 234 doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation) 235 .getAnimationResId(any(), anyInt(), anyInt()); 236 doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation) 237 .getDefaultAnimationResId(anyInt(), anyInt()); 238 239 BackNavigationInfo backNavigationInfo = startBackNavigation(); 240 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 241 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 242 assertThat(backNavigationInfo.getCustomAnimationInfo().getWindowAnimations()) 243 .isEqualTo(testCase.windowFront.mAttrs.windowAnimations); 244 assertThat(typeToString(backNavigationInfo.getType())) 245 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); 246 // verify if back animation would start. 247 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 248 } 249 250 @Test backTypeCrossActivityWhenBackToPreviousActivity()251 public void backTypeCrossActivityWhenBackToPreviousActivity() { 252 CrossActivityTestCase testCase = createTopTaskWithTwoActivities(); 253 IOnBackInvokedCallback callback = withSystemCallback(testCase.task); 254 255 BackNavigationInfo backNavigationInfo = startBackNavigation(); 256 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 257 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 258 assertThat(typeToString(backNavigationInfo.getType())) 259 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); 260 // verify if back animation would start. 261 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 262 263 // reset drawing status 264 testCase.recordBack.setState(STOPPED, "stopped"); 265 backNavigationInfo.onBackNavigationFinished(false); 266 mBackNavigationController.clearBackAnimations(true); 267 makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); 268 setupKeyguardOccluded(); 269 backNavigationInfo = startBackNavigation(); 270 assertThat(typeToString(backNavigationInfo.getType())) 271 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 272 273 // reset drawing status, test if top activity is translucent 274 backNavigationInfo.onBackNavigationFinished(false); 275 mBackNavigationController.clearBackAnimations(true); 276 makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); 277 // simulate translucent 278 testCase.recordFront.setOccludesParent(false); 279 backNavigationInfo = startBackNavigation(); 280 assertThat(typeToString(backNavigationInfo.getType())) 281 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 282 testCase.recordFront.setOccludesParent(true); 283 284 // reset drawing status, test if bottom activity is translucent 285 backNavigationInfo.onBackNavigationFinished(false); 286 mBackNavigationController.clearBackAnimations(true); 287 makeWindowVisibleAndDrawn(testCase.recordBack.findMainWindow()); 288 // simulate translucent 289 testCase.recordBack.setOccludesParent(false); 290 backNavigationInfo = startBackNavigation(); 291 assertThat(typeToString(backNavigationInfo.getType())) 292 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 293 testCase.recordBack.setOccludesParent(true); 294 295 // reset drawing status, test canShowWhenLocked 296 backNavigationInfo.onBackNavigationFinished(false); 297 mBackNavigationController.clearBackAnimations(true); 298 doReturn(true).when(testCase.recordBack).canShowWhenLocked(); 299 backNavigationInfo = startBackNavigation(); 300 assertThat(typeToString(backNavigationInfo.getType())) 301 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); 302 } 303 304 @Test backTypeCrossActivityInTaskFragment()305 public void backTypeCrossActivityInTaskFragment() { 306 final Task task = createTask(mDefaultDisplay); 307 final TaskFragment tf1 = createTaskFragmentWithActivity(task); 308 final TaskFragment tf2 = createTaskFragmentWithActivity(task); 309 final ArrayList<ActivityRecord> outPrevActivities = new ArrayList<>(); 310 311 ActivityRecord prevAr = tf1.getTopMostActivity(); 312 ActivityRecord topAr = tf2.getTopMostActivity(); 313 boolean predictable; 314 315 // Stacked + no Companion => predict for previous activity. 316 // TF2 317 // TF1 318 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 319 outPrevActivities); 320 assertTrue(outPrevActivities.contains(prevAr)); 321 assertTrue(predictable); 322 outPrevActivities.clear(); 323 324 // Stacked + top companion to bottom but bottom didn't => predict for previous activity 325 tf2.setCompanionTaskFragment(tf1); 326 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 327 outPrevActivities); 328 assertTrue(outPrevActivities.contains(prevAr)); 329 assertTrue(predictable); 330 tf2.setCompanionTaskFragment(null); 331 outPrevActivities.clear(); 332 333 // Stacked + next companion to top => predict for previous task 334 tf1.setCompanionTaskFragment(tf2); 335 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 336 outPrevActivities); 337 assertTrue(outPrevActivities.isEmpty()); 338 assertTrue(predictable); 339 tf1.setCompanionTaskFragment(null); 340 341 // Adjacent + no companion => unable to predict 342 // TF1 | TF2 343 tf1.setAdjacentTaskFragment(tf2); 344 tf2.setAdjacentTaskFragment(tf1); 345 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 346 outPrevActivities); 347 assertTrue(outPrevActivities.isEmpty()); 348 assertFalse(predictable); 349 predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, 350 outPrevActivities); 351 assertTrue(outPrevActivities.isEmpty()); 352 assertFalse(predictable); 353 354 // Adjacent + companion => predict for previous task 355 tf1.setCompanionTaskFragment(tf2); 356 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 357 outPrevActivities); 358 assertTrue(outPrevActivities.isEmpty()); 359 assertTrue(predictable); 360 tf1.setCompanionTaskFragment(null); 361 362 tf2.setCompanionTaskFragment(tf1); 363 predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, 364 outPrevActivities); 365 assertTrue(outPrevActivities.isEmpty()); 366 assertTrue(predictable); 367 // reset 368 tf1.setAdjacentTaskFragment(null); 369 tf2.setAdjacentTaskFragment(null); 370 tf1.setCompanionTaskFragment(null); 371 tf2.setCompanionTaskFragment(null); 372 373 final TaskFragment tf3 = new TaskFragmentBuilder(mAtm) 374 .createActivityCount(2) 375 .setParentTask(task) 376 .build(); 377 topAr = tf3.getTopMostActivity(); 378 prevAr = tf3.getBottomMostActivity(); 379 // Stacked => predict for previous activity. 380 // TF3 381 // TF2 382 // TF1 383 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 384 outPrevActivities); 385 assertTrue(outPrevActivities.contains(prevAr)); 386 assertTrue(predictable); 387 // reset 388 outPrevActivities.clear(); 389 390 // Adjacent => predict for previous activity. 391 // TF2 | TF3 392 // TF1 393 tf2.setAdjacentTaskFragment(tf3); 394 tf3.setAdjacentTaskFragment(tf2); 395 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 396 outPrevActivities); 397 assertTrue(outPrevActivities.contains(prevAr)); 398 assertTrue(predictable); 399 // reset 400 outPrevActivities.clear(); 401 tf2.setAdjacentTaskFragment(null); 402 tf3.setAdjacentTaskFragment(null); 403 404 final TaskFragment tf4 = createTaskFragmentWithActivity(task); 405 // Stacked + next companion to top => predict for previous activity below companion. 406 // Tf4 407 // TF3 408 // TF2 409 // TF1 410 tf3.setCompanionTaskFragment(tf4); 411 topAr = tf4.getTopMostActivity(); 412 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 413 outPrevActivities); 414 assertTrue(outPrevActivities.contains(tf2.getTopMostActivity())); 415 assertTrue(predictable); 416 outPrevActivities.clear(); 417 tf3.setCompanionTaskFragment(null); 418 419 // Stacked + top companion to next but next one didn't => predict for previous activity. 420 tf4.setCompanionTaskFragment(tf3); 421 topAr = tf4.getTopMostActivity(); 422 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, 423 outPrevActivities); 424 assertTrue(outPrevActivities.contains(tf3.getTopMostActivity())); 425 assertTrue(predictable); 426 } 427 428 @Test backTypeDialogCloseWhenBackFromDialog()429 public void backTypeDialogCloseWhenBackFromDialog() { 430 DialogCloseTestCase testCase = createTopTaskWithActivityAndDialog(); 431 IOnBackInvokedCallback callback = withSystemCallback(testCase.task); 432 433 BackNavigationInfo backNavigationInfo = startBackNavigation(); 434 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 435 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 436 assertThat(typeToString(backNavigationInfo.getType())) 437 .isEqualTo(typeToString(BackNavigationInfo.TYPE_DIALOG_CLOSE)); 438 // verify if back animation would start. 439 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 440 } 441 442 @Test backInfoWithNullWindow()443 public void backInfoWithNullWindow() { 444 BackNavigationInfo backNavigationInfo = startBackNavigation(); 445 assertThat(backNavigationInfo).isNull(); 446 } 447 448 @Test backInfoWindowWithNoActivity()449 public void backInfoWindowWithNoActivity() { 450 WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER, 451 "Wallpaper"); 452 addToWindowMap(window, true); 453 makeWindowVisibleAndDrawn(window); 454 455 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 456 window.setOnBackInvokedCallbackInfo( 457 new OnBackInvokedCallbackInfo( 458 callback, 459 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 460 /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); 461 462 BackNavigationInfo backNavigationInfo = startBackNavigation(); 463 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 464 assertThat(backNavigationInfo.getType()).isEqualTo(BackNavigationInfo.TYPE_CALLBACK); 465 assertThat(backNavigationInfo.isAnimationCallback()).isEqualTo(false); 466 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 467 } 468 469 @Test backInfoWithAnimationCallback()470 public void backInfoWithAnimationCallback() { 471 WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER, 472 "Wallpaper"); 473 addToWindowMap(window, true); 474 makeWindowVisibleAndDrawn(window); 475 476 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 477 window.setOnBackInvokedCallbackInfo( 478 new OnBackInvokedCallbackInfo( 479 callback, 480 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 481 /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED)); 482 483 BackNavigationInfo backNavigationInfo = startBackNavigation(); 484 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 485 assertThat(backNavigationInfo.getType()).isEqualTo(BackNavigationInfo.TYPE_CALLBACK); 486 assertThat(backNavigationInfo.isAnimationCallback()).isEqualTo(true); 487 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 488 } 489 490 @Test preparesForBackToHome()491 public void preparesForBackToHome() { 492 final Task topTask = createTopTaskWithActivity(); 493 withSystemCallback(topTask); 494 495 BackNavigationInfo backNavigationInfo = startBackNavigation(); 496 assertThat(typeToString(backNavigationInfo.getType())) 497 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); 498 499 backNavigationInfo.onBackNavigationFinished(false); 500 mBackNavigationController.clearBackAnimations(true); 501 502 final WindowState window = topTask.getTopVisibleAppMainWindow(); 503 makeWindowVisibleAndDrawn(window); 504 setupKeyguardOccluded(); 505 backNavigationInfo = startBackNavigation(); 506 assertThat(typeToString(backNavigationInfo.getType())) 507 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 508 } 509 510 @Test backTypeCallback()511 public void backTypeCallback() { 512 Task task = createTopTaskWithActivity(); 513 IOnBackInvokedCallback appCallback = withAppCallback(task); 514 515 BackNavigationInfo backNavigationInfo = startBackNavigation(); 516 assertThat(typeToString(backNavigationInfo.getType())) 517 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 518 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(appCallback); 519 } 520 521 // TODO (b/259427810) Remove this test when we figure out new API 522 @Test backAnimationSkipSharedElementTransition()523 public void backAnimationSkipSharedElementTransition() { 524 // Simulate ActivityOptions#makeSceneTransitionAnimation 525 final Bundle myBundle = new Bundle(); 526 myBundle.putInt(ActivityOptions.KEY_ANIM_TYPE, ANIM_SCENE_TRANSITION); 527 final ActivityOptions options = new ActivityOptions(myBundle); 528 final ActivityOptions.SceneTransitionInfo info = new ActivityOptions.SceneTransitionInfo(); 529 info.setResultReceiver(mock(android.os.ResultReceiver.class)); 530 options.setSceneTransitionInfo(info); 531 532 final ActivityRecord testActivity = new ActivityBuilder(mAtm) 533 .setCreateTask(true) 534 .setActivityOptions(options) 535 .build(); 536 testActivity.info.applicationInfo.privateFlagsExt |= 537 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 538 final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, testActivity, 539 "window"); 540 addToWindowMap(window, true); 541 makeWindowVisibleAndDrawn(window); 542 IOnBackInvokedCallback callback = withSystemCallback(testActivity.getTask()); 543 544 BackNavigationInfo backNavigationInfo = startBackNavigation(); 545 assertTrue(testActivity.mHasSceneTransition); 546 assertThat(typeToString(backNavigationInfo.getType())) 547 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 548 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 549 } 550 551 @Test testUnregisterCallbacksWithSystemCallback()552 public void testUnregisterCallbacksWithSystemCallback() 553 throws InterruptedException, RemoteException { 554 CountDownLatch systemLatch = new CountDownLatch(1); 555 CountDownLatch appLatch = new CountDownLatch(1); 556 557 final ApplicationInfo info = mock(ApplicationInfo.class); 558 final Context context = mock(Context.class); 559 Mockito.doReturn(true).when(info).isOnBackInvokedCallbackEnabled(); 560 Mockito.doReturn(info).when(context).getApplicationInfo(); 561 562 Task task = createTopTaskWithActivity(); 563 WindowState appWindow = task.getTopVisibleAppMainWindow(); 564 WindowOnBackInvokedDispatcher dispatcher = 565 new WindowOnBackInvokedDispatcher(context, Looper.getMainLooper()); 566 spyOn(appWindow.mSession); 567 doAnswer(invocation -> { 568 appWindow.setOnBackInvokedCallbackInfo(invocation.getArgument(1)); 569 return null; 570 }).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any()); 571 572 addToWindowMap(appWindow, true); 573 dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient, null, null); 574 575 576 OnBackInvokedCallback appCallback = createBackCallback(appLatch); 577 OnBackInvokedCallback systemCallback = createBackCallback(systemLatch); 578 579 // Register both a system callback and an application callback 580 dispatcher.registerSystemOnBackInvokedCallback(systemCallback); 581 dispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, 582 appCallback); 583 584 // Check that the top callback is the app callback 585 assertEquals(appCallback, dispatcher.getTopCallback()); 586 587 // Now unregister the app callback and check that the top callback is the system callback 588 dispatcher.unregisterOnBackInvokedCallback(appCallback); 589 assertEquals(systemCallback, dispatcher.getTopCallback()); 590 591 // Verify that this has correctly been propagated to the server and that the 592 // BackNavigationInfo object will contain the system callback 593 BackNavigationInfo backNavigationInfo = startBackNavigation(); 594 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 595 IOnBackInvokedCallback callback = backNavigationInfo.getOnBackInvokedCallback(); 596 assertThat(callback).isNotNull(); 597 598 try { 599 callback.onBackInvoked(); 600 } catch (RemoteException e) { 601 throw new RuntimeException(e); 602 } 603 604 // Check that the system callback has been call 605 assertTrue("System callback has not been called", 606 systemLatch.await(500, TimeUnit.MILLISECONDS)); 607 assertEquals("App callback should not have been called", 608 1, appLatch.getCount()); 609 } 610 611 @Test backInfoWindowWithoutDrawn()612 public void backInfoWindowWithoutDrawn() { 613 WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_APPLICATION, 614 "TestWindow"); 615 addToWindowMap(window, true); 616 617 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 618 window.setOnBackInvokedCallbackInfo( 619 new OnBackInvokedCallbackInfo( 620 callback, 621 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 622 /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); 623 624 BackNavigationInfo backNavigationInfo = startBackNavigation(); 625 assertThat(backNavigationInfo).isNull(); 626 } 627 628 @Test 629 @RequiresFlagsDisabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION) testTransitionHappensCancelNavigation()630 public void testTransitionHappensCancelNavigation() { 631 // Create a floating task and a fullscreen task, then navigating on fullscreen task. 632 // The navigation should not been cancelled when transition happens on floating task, and 633 // only be cancelled when transition happens on the navigating task. 634 final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, 635 ACTIVITY_TYPE_STANDARD); 636 final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask); 637 638 final Task fullscreenTask = createTopTaskWithActivity(); 639 withSystemCallback(fullscreenTask); 640 final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity(); 641 642 final CountDownLatch navigationObserver = new CountDownLatch(1); 643 startBackNavigation(navigationObserver); 644 645 final ArraySet<ActivityRecord> opening = new ArraySet<>(); 646 final ArraySet<ActivityRecord> closing = new ArraySet<>(); 647 final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask); 648 opening.add(secondFloatingActivity); 649 closing.add(baseFloatingActivity); 650 mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); 651 assertEquals("Transition happen on an irrelevant task, callback should not been called", 652 1, navigationObserver.getCount()); 653 654 // Create a new activity above navigation target, the transition should cancel navigation. 655 final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask); 656 opening.clear(); 657 closing.clear(); 658 opening.add(topFullscreenActivity); 659 closing.add(baseFullscreenActivity); 660 mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); 661 assertEquals("Transition happen on navigation task, callback should have been called", 662 0, navigationObserver.getCount()); 663 } 664 665 @Test testWindowFocusChangeCancelNavigation()666 public void testWindowFocusChangeCancelNavigation() { 667 Task task = createTopTaskWithActivity(); 668 withSystemCallback(task); 669 WindowState focusWindow = task.getTopVisibleAppMainWindow(); 670 final CountDownLatch navigationObserver = new CountDownLatch(1); 671 startBackNavigation(navigationObserver); 672 673 mBackNavigationController.onFocusChanged(null); 674 assertEquals("change focus to null, callback should not have been called", 675 1, navigationObserver.getCount()); 676 mBackNavigationController.onFocusChanged(focusWindow); 677 assertEquals("change focus back, callback should not have been called", 678 1, navigationObserver.getCount()); 679 680 WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow"); 681 addToWindowMap(newWindow, true); 682 mBackNavigationController.onFocusChanged(newWindow); 683 assertEquals("Focus change, callback should have been called", 684 0, navigationObserver.getCount()); 685 } 686 687 @Test testBackOnMostRecentWindowInActivityEmbedding()688 public void testBackOnMostRecentWindowInActivityEmbedding() { 689 final Task task = createTask(mDefaultDisplay); 690 final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); 691 final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); 692 final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); 693 final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); 694 final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); 695 primaryTf.setAdjacentTaskFragment(secondaryTf); 696 secondaryTf.setAdjacentTaskFragment(primaryTf); 697 698 final WindowState primaryWindow = mock(WindowState.class); 699 final WindowState secondaryWindow = mock(WindowState.class); 700 doReturn(primaryActivity).when(primaryWindow).getActivityRecord(); 701 doReturn(secondaryActivity).when(secondaryWindow).getActivityRecord(); 702 doReturn(1L).when(primaryActivity).getLastWindowCreateTime(); 703 doReturn(2L).when(secondaryActivity).getLastWindowCreateTime(); 704 doReturn(mDisplayContent).when(primaryActivity).getDisplayContent(); 705 doReturn(secondaryWindow).when(mDisplayContent).findFocusedWindow(eq(secondaryActivity)); 706 707 final WindowState mostRecentUsedWindow = 708 mWm.getMostRecentUsedEmbeddedWindowForBack(primaryWindow); 709 assertThat(mostRecentUsedWindow).isEqualTo(secondaryWindow); 710 } 711 712 /** 713 * Test with 714 * config_predictShowStartingSurface = true 715 */ 716 @Test testEnableWindowlessSurface()717 public void testEnableWindowlessSurface() { 718 testPrepareAnimation(true); 719 } 720 721 /** 722 * Test with 723 * config_predictShowStartingSurface = false 724 */ 725 @Test testDisableWindowlessSurface()726 public void testDisableWindowlessSurface() { 727 testPrepareAnimation(false); 728 } 729 withSystemCallback(Task task)730 private IOnBackInvokedCallback withSystemCallback(Task task) { 731 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 732 task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( 733 new OnBackInvokedCallbackInfo( 734 callback, 735 OnBackInvokedDispatcher.PRIORITY_SYSTEM, 736 /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); 737 return callback; 738 } 739 withAppCallback(Task task)740 private IOnBackInvokedCallback withAppCallback(Task task) { 741 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 742 task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( 743 new OnBackInvokedCallbackInfo( 744 callback, 745 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 746 /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); 747 return callback; 748 } 749 750 @Nullable startBackNavigation()751 private BackNavigationInfo startBackNavigation() { 752 return mBackNavigationController.startBackNavigation( 753 createNavigationObserver(null), mBackAnimationAdapter); 754 } 755 756 @Nullable startBackNavigation(CountDownLatch navigationObserverLatch)757 private BackNavigationInfo startBackNavigation(CountDownLatch navigationObserverLatch) { 758 return mBackNavigationController.startBackNavigation( 759 createNavigationObserver(navigationObserverLatch), mBackAnimationAdapter); 760 } 761 762 @NonNull createOnBackInvokedCallback()763 private IOnBackInvokedCallback createOnBackInvokedCallback() { 764 return new IOnBackInvokedCallback.Stub() { 765 @Override 766 public void onBackStarted(BackMotionEvent backMotionEvent) { 767 } 768 769 @Override 770 public void onBackProgressed(BackMotionEvent backMotionEvent) { 771 } 772 773 @Override 774 public void onBackCancelled() { 775 } 776 777 @Override 778 public void onBackInvoked() { 779 } 780 781 @Override 782 public void setTriggerBack(boolean triggerBack) { 783 } 784 785 @Override 786 public void setHandoffHandler(IBackAnimationHandoffHandler unused) { 787 } 788 }; 789 } 790 createBackCallback(CountDownLatch latch)791 private OnBackInvokedCallback createBackCallback(CountDownLatch latch) { 792 return new OnBackInvokedCallback() { 793 @Override 794 public void onBackInvoked() { 795 if (latch != null) { 796 latch.countDown(); 797 } 798 } 799 }; 800 } 801 802 private RemoteCallback createNavigationObserver(CountDownLatch latch) { 803 return new RemoteCallback(result -> { 804 if (latch != null) { 805 latch.countDown(); 806 } 807 }); 808 } 809 810 private Task initHomeActivity() { 811 final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); 812 task.forAllLeafTasks((t) -> { 813 if (t.getTopMostActivity() == null) { 814 final ActivityRecord r = createActivityRecord(t); 815 Mockito.doNothing().when(t).reparentSurfaceControl(any(), any()); 816 Mockito.doNothing().when(r).reparentSurfaceControl(any(), any()); 817 } 818 }, true); 819 return task; 820 } 821 822 private void setupKeyguardOccluded() { 823 final KeyguardController kc = mRootHomeTask.mTaskSupervisor.getKeyguardController(); 824 doReturn(true).when(kc).isKeyguardLocked(anyInt()); 825 doReturn(true).when(kc).isKeyguardOccluded(anyInt()); 826 } 827 828 private void testPrepareAnimation(boolean preferWindowlessSurface) { 829 final TaskSnapshot taskSnapshot = mock(TaskSnapshot.class); 830 final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext)); 831 final Resources resourcesSpy = Mockito.spy(contextSpy.getResources()); 832 833 spyOn(mAtm.mTaskOrganizerController); 834 when(contextSpy.getResources()).thenReturn(resourcesSpy); 835 836 MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class) 837 .strictness(Strictness.LENIENT).startMocking(); 838 doReturn(taskSnapshot).when(() -> BackNavigationController.getSnapshot(any(), any())); 839 when(resourcesSpy.getBoolean( 840 com.android.internal.R.bool.config_predictShowStartingSurface)) 841 .thenReturn(preferWindowlessSurface); 842 843 final BackNavigationController.AnimationHandler animationHandler = 844 Mockito.spy(new BackNavigationController.AnimationHandler(mWm)); 845 doReturn(true).when(animationHandler).isSupportWindowlessSurface(); 846 testWithConfig(animationHandler, preferWindowlessSurface); 847 mockitoSession.finishMocking(); 848 } 849 850 private void testWithConfig(BackNavigationController.AnimationHandler animationHandler, 851 boolean preferWindowlessSurface) { 852 final Task task = createTask(mDefaultDisplay); 853 final ActivityRecord bottomActivity = createActivityRecord(task); 854 final ActivityRecord homeActivity = mRootHomeTask.getTopNonFinishingActivity(); 855 final ArrayList<ActivityRecord> openActivities = new ArrayList<>(); 856 openActivities.add(homeActivity); 857 final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toHomeBuilder = 858 animationHandler.prepareAnimation( 859 BackNavigationInfo.TYPE_RETURN_TO_HOME, 860 mBackAnimationAdapter, 861 mNavigationMonitor, 862 task, 863 mRootHomeTask, 864 bottomActivity, 865 openActivities, 866 task); 867 assertTrue(toHomeBuilder.mIsLaunchBehind); 868 toHomeBuilder.build(); 869 verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface( 870 any(), any(), any(), any(), any(), any()); 871 animationHandler.clearBackAnimateTarget(true); 872 openActivities.clear(); 873 874 // Back to ACTIVITY and TASK have the same logic, just with different target. 875 final ActivityRecord topActivity = createActivityRecord(task); 876 openActivities.add(bottomActivity); 877 final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toActivityBuilder = 878 animationHandler.prepareAnimation( 879 BackNavigationInfo.TYPE_CROSS_ACTIVITY, 880 mBackAnimationAdapter, 881 mNavigationMonitor, 882 task, 883 task, 884 topActivity, 885 openActivities, 886 topActivity); 887 assertFalse(toActivityBuilder.mIsLaunchBehind); 888 toActivityBuilder.build(); 889 if (preferWindowlessSurface) { 890 verify(mAtm.mTaskOrganizerController).addWindowlessStartingSurface( 891 any(), any(), any(), any(), any(), any()); 892 } else { 893 verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface( 894 any(), any(), any(), any(), any(), any()); 895 } 896 } 897 898 @NonNull 899 private Task createTopTaskWithActivity() { 900 Task task = createTask(mDefaultDisplay); 901 ActivityRecord record = createActivityRecord(task); 902 // enable OnBackInvokedCallbacks 903 record.info.applicationInfo.privateFlagsExt |= 904 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 905 WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); 906 when(record.mSurfaceControl.isValid()).thenReturn(true); 907 Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); 908 mAtm.setFocusedTask(task.mTaskId, record); 909 addToWindowMap(window, true); 910 makeWindowVisibleAndDrawn(window); 911 return task; 912 } 913 914 @NonNull 915 private DialogCloseTestCase createTopTaskWithActivityAndDialog() { 916 Task task = createTask(mDefaultDisplay); 917 ActivityRecord record = createActivityRecord(task); 918 // enable OnBackInvokedCallbacks 919 record.info.applicationInfo.privateFlagsExt |= 920 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 921 WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); 922 WindowState dialog = createWindow(null, TYPE_APPLICATION, record, "dialog"); 923 when(record.mSurfaceControl.isValid()).thenReturn(true); 924 Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); 925 mAtm.setFocusedTask(task.mTaskId, record); 926 addToWindowMap(window, true); 927 addToWindowMap(dialog, true); 928 929 makeWindowVisibleAndDrawn(dialog); 930 931 DialogCloseTestCase testCase = new DialogCloseTestCase(); 932 testCase.task = task; 933 testCase.record = record; 934 testCase.windowBack = window; 935 testCase.windowFront = dialog; 936 return testCase; 937 } 938 939 @NonNull 940 private CrossActivityTestCase createTopTaskWithTwoActivities() { 941 Task task = createTask(mDefaultDisplay); 942 ActivityRecord record1 = createActivityRecord(task); 943 ActivityRecord record2 = createActivityRecord(task); 944 // enable OnBackInvokedCallbacks 945 record2.info.applicationInfo.privateFlagsExt |= 946 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 947 WindowState window1 = createWindow(null, FIRST_APPLICATION_WINDOW, record1, "window1"); 948 WindowState window2 = createWindow(null, FIRST_APPLICATION_WINDOW, record2, "window2"); 949 when(task.mSurfaceControl.isValid()).thenReturn(true); 950 when(record1.mSurfaceControl.isValid()).thenReturn(true); 951 when(record2.mSurfaceControl.isValid()).thenReturn(true); 952 Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); 953 Mockito.doNothing().when(record1).reparentSurfaceControl(any(), any()); 954 Mockito.doNothing().when(record2).reparentSurfaceControl(any(), any()); 955 mAtm.setFocusedTask(task.mTaskId, record1); 956 mAtm.setFocusedTask(task.mTaskId, record2); 957 addToWindowMap(window1, true); 958 addToWindowMap(window2, true); 959 960 makeWindowVisibleAndDrawn(window2); 961 962 CrossActivityTestCase testCase = new CrossActivityTestCase(); 963 testCase.task = task; 964 testCase.recordBack = record1; 965 testCase.recordFront = record2; 966 testCase.windowBack = window1; 967 testCase.windowFront = window2; 968 record1.setState(STOPPED, "stopped"); 969 return testCase; 970 } 971 972 private void addToWindowMap(WindowState window, boolean focus) { 973 mWm.mWindowMap.put(window.mClient.asBinder(), window); 974 if (focus) { 975 doReturn(window.getWindowInfo().token) 976 .when(mWindowManagerInternal).getFocusedWindowToken(); 977 doReturn(window).when(mWm).getFocusedWindowLocked(); 978 } 979 } 980 981 private class CrossActivityTestCase { 982 public Task task; 983 public ActivityRecord recordBack; 984 public WindowState windowBack; 985 public ActivityRecord recordFront; 986 public WindowState windowFront; 987 } 988 989 private class DialogCloseTestCase { 990 public Task task; 991 public ActivityRecord record; 992 public WindowState windowBack; 993 public WindowState windowFront; 994 } 995 } 996