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_OVERLAY; 25 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 26 import static android.window.BackNavigationInfo.typeToString; 27 28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 32 33 import static com.google.common.truth.Truth.assertThat; 34 import static com.google.common.truth.Truth.assertWithMessage; 35 36 import static org.junit.Assert.assertEquals; 37 import static org.junit.Assert.assertFalse; 38 import static org.junit.Assert.assertTrue; 39 import static org.mockito.ArgumentMatchers.any; 40 import static org.mockito.ArgumentMatchers.anyInt; 41 import static org.mockito.ArgumentMatchers.eq; 42 import static org.mockito.Mockito.doAnswer; 43 import static org.mockito.Mockito.mock; 44 import static org.mockito.Mockito.never; 45 import static org.mockito.Mockito.when; 46 47 import android.annotation.NonNull; 48 import android.annotation.Nullable; 49 import android.app.ActivityOptions; 50 import android.content.Context; 51 import android.content.ContextWrapper; 52 import android.content.pm.ApplicationInfo; 53 import android.content.res.Resources; 54 import android.os.Bundle; 55 import android.os.RemoteCallback; 56 import android.os.RemoteException; 57 import android.platform.test.annotations.Presubmit; 58 import android.util.ArraySet; 59 import android.view.WindowManager; 60 import android.window.BackAnimationAdapter; 61 import android.window.BackMotionEvent; 62 import android.window.BackNavigationInfo; 63 import android.window.IOnBackInvokedCallback; 64 import android.window.OnBackInvokedCallback; 65 import android.window.OnBackInvokedCallbackInfo; 66 import android.window.OnBackInvokedDispatcher; 67 import android.window.TaskSnapshot; 68 import android.window.WindowOnBackInvokedDispatcher; 69 70 import com.android.server.LocalServices; 71 72 import org.junit.Before; 73 import org.junit.Test; 74 import org.junit.runner.RunWith; 75 import org.mockito.Mockito; 76 import org.mockito.MockitoSession; 77 import org.mockito.quality.Strictness; 78 79 import java.util.concurrent.CountDownLatch; 80 import java.util.concurrent.TimeUnit; 81 82 @Presubmit 83 @RunWith(WindowTestRunner.class) 84 public class BackNavigationControllerTests extends WindowTestsBase { 85 private BackNavigationController mBackNavigationController; 86 private WindowManagerInternal mWindowManagerInternal; 87 private BackAnimationAdapter mBackAnimationAdapter; 88 private Task mRootHomeTask; 89 90 @Before setUp()91 public void setUp() throws Exception { 92 final BackNavigationController original = new BackNavigationController(); 93 original.setWindowManager(mWm); 94 mBackNavigationController = Mockito.spy(original); 95 LocalServices.removeServiceForTest(WindowManagerInternal.class); 96 mWindowManagerInternal = mock(WindowManagerInternal.class); 97 LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); 98 mBackAnimationAdapter = mock(BackAnimationAdapter.class); 99 mRootHomeTask = initHomeActivity(); 100 } 101 102 @Test backNavInfo_HomeWhenBackToLauncher()103 public void backNavInfo_HomeWhenBackToLauncher() { 104 Task task = createTopTaskWithActivity(); 105 IOnBackInvokedCallback callback = withSystemCallback(task); 106 107 BackNavigationInfo backNavigationInfo = startBackNavigation(); 108 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 109 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 110 assertThat(typeToString(backNavigationInfo.getType())) 111 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); 112 113 // verify if back animation would start. 114 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 115 } 116 117 @Test backTypeCrossTaskWhenBackToPreviousTask()118 public void backTypeCrossTaskWhenBackToPreviousTask() { 119 Task taskA = createTask(mDefaultDisplay); 120 ActivityRecord recordA = createActivityRecord(taskA); 121 Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any()); 122 123 final Task topTask = createTopTaskWithActivity(); 124 withSystemCallback(topTask); 125 BackNavigationInfo backNavigationInfo = startBackNavigation(); 126 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 127 assertThat(typeToString(backNavigationInfo.getType())) 128 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK)); 129 130 // verify if back animation would start. 131 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 132 133 // reset drawing status 134 backNavigationInfo.onBackNavigationFinished(false); 135 mBackNavigationController.clearBackAnimations(); 136 topTask.forAllWindows(w -> { 137 makeWindowVisibleAndDrawn(w); 138 }, true); 139 setupKeyguardOccluded(); 140 backNavigationInfo = startBackNavigation(); 141 assertThat(typeToString(backNavigationInfo.getType())) 142 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 143 144 backNavigationInfo.onBackNavigationFinished(false); 145 mBackNavigationController.clearBackAnimations(); 146 doReturn(true).when(recordA).canShowWhenLocked(); 147 backNavigationInfo = startBackNavigation(); 148 assertThat(typeToString(backNavigationInfo.getType())) 149 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK)); 150 } 151 152 @Test backTypeBackToHomeDifferentUser()153 public void backTypeBackToHomeDifferentUser() { 154 Task taskA = createTask(mDefaultDisplay); 155 ActivityRecord recordA = createActivityRecord(taskA); 156 Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any()); 157 doReturn(false).when(taskA).showToCurrentUser(); 158 159 withSystemCallback(createTopTaskWithActivity()); 160 BackNavigationInfo backNavigationInfo = startBackNavigation(); 161 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 162 assertThat(typeToString(backNavigationInfo.getType())) 163 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); 164 } 165 166 @Test backTypeCrossActivityWithCustomizeExitAnimation()167 public void backTypeCrossActivityWithCustomizeExitAnimation() { 168 CrossActivityTestCase testCase = createTopTaskWithTwoActivities(); 169 IOnBackInvokedCallback callback = withSystemCallback(testCase.task); 170 testCase.windowFront.mAttrs.windowAnimations = 0x10; 171 spyOn(mDisplayContent.mAppTransition.mTransitionAnimation); 172 doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation) 173 .getAnimationResId(any(), anyInt(), anyInt()); 174 doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation) 175 .getDefaultAnimationResId(anyInt(), anyInt()); 176 177 BackNavigationInfo backNavigationInfo = startBackNavigation(); 178 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 179 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 180 assertThat(backNavigationInfo.getCustomAnimationInfo().getWindowAnimations()) 181 .isEqualTo(testCase.windowFront.mAttrs.windowAnimations); 182 assertThat(typeToString(backNavigationInfo.getType())) 183 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); 184 // verify if back animation would start. 185 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 186 } 187 188 @Test backTypeCrossActivityWhenBackToPreviousActivity()189 public void backTypeCrossActivityWhenBackToPreviousActivity() { 190 CrossActivityTestCase testCase = createTopTaskWithTwoActivities(); 191 IOnBackInvokedCallback callback = withSystemCallback(testCase.task); 192 193 BackNavigationInfo backNavigationInfo = startBackNavigation(); 194 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 195 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 196 assertThat(typeToString(backNavigationInfo.getType())) 197 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); 198 // verify if back animation would start. 199 assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); 200 201 // reset drawing status 202 backNavigationInfo.onBackNavigationFinished(false); 203 mBackNavigationController.clearBackAnimations(); 204 testCase.recordFront.forAllWindows(w -> { 205 makeWindowVisibleAndDrawn(w); 206 }, true); 207 setupKeyguardOccluded(); 208 backNavigationInfo = startBackNavigation(); 209 assertThat(typeToString(backNavigationInfo.getType())) 210 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 211 212 backNavigationInfo.onBackNavigationFinished(false); 213 mBackNavigationController.clearBackAnimations(); 214 doReturn(true).when(testCase.recordBack).canShowWhenLocked(); 215 backNavigationInfo = startBackNavigation(); 216 assertThat(typeToString(backNavigationInfo.getType())) 217 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); 218 } 219 220 @Test backInfoWithNullWindow()221 public void backInfoWithNullWindow() { 222 BackNavigationInfo backNavigationInfo = startBackNavigation(); 223 assertThat(backNavigationInfo).isNull(); 224 } 225 226 @Test backInfoWindowWithNoActivity()227 public void backInfoWindowWithNoActivity() { 228 WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER, 229 "Wallpaper"); 230 addToWindowMap(window, true); 231 makeWindowVisibleAndDrawn(window); 232 233 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 234 window.setOnBackInvokedCallbackInfo( 235 new OnBackInvokedCallbackInfo( 236 callback, 237 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 238 /* isAnimationCallback = */ false)); 239 240 BackNavigationInfo backNavigationInfo = startBackNavigation(); 241 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 242 assertThat(backNavigationInfo.getType()).isEqualTo(BackNavigationInfo.TYPE_CALLBACK); 243 assertThat(backNavigationInfo.isAnimationCallback()).isEqualTo(false); 244 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 245 } 246 247 @Test backInfoWithAnimationCallback()248 public void backInfoWithAnimationCallback() { 249 WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER, 250 "Wallpaper"); 251 addToWindowMap(window, true); 252 makeWindowVisibleAndDrawn(window); 253 254 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 255 window.setOnBackInvokedCallbackInfo( 256 new OnBackInvokedCallbackInfo( 257 callback, 258 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 259 /* isAnimationCallback = */ true)); 260 261 BackNavigationInfo backNavigationInfo = startBackNavigation(); 262 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 263 assertThat(backNavigationInfo.getType()).isEqualTo(BackNavigationInfo.TYPE_CALLBACK); 264 assertThat(backNavigationInfo.isAnimationCallback()).isEqualTo(true); 265 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 266 } 267 268 @Test preparesForBackToHome()269 public void preparesForBackToHome() { 270 final Task topTask = createTopTaskWithActivity(); 271 withSystemCallback(topTask); 272 273 BackNavigationInfo backNavigationInfo = startBackNavigation(); 274 assertThat(typeToString(backNavigationInfo.getType())) 275 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); 276 277 backNavigationInfo.onBackNavigationFinished(false); 278 mBackNavigationController.clearBackAnimations(); 279 setupKeyguardOccluded(); 280 backNavigationInfo = startBackNavigation(); 281 assertThat(typeToString(backNavigationInfo.getType())) 282 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 283 } 284 285 @Test backTypeCallback()286 public void backTypeCallback() { 287 Task task = createTopTaskWithActivity(); 288 IOnBackInvokedCallback appCallback = withAppCallback(task); 289 290 BackNavigationInfo backNavigationInfo = startBackNavigation(); 291 assertThat(typeToString(backNavigationInfo.getType())) 292 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 293 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(appCallback); 294 } 295 296 // TODO (b/259427810) Remove this test when we figure out new API 297 @Test backAnimationSkipSharedElementTransition()298 public void backAnimationSkipSharedElementTransition() { 299 // Simulate ActivityOptions#makeSceneTransitionAnimation 300 final Bundle myBundle = new Bundle(); 301 myBundle.putInt(ActivityOptions.KEY_ANIM_TYPE, ANIM_SCENE_TRANSITION); 302 myBundle.putParcelable("android:activity.transitionCompleteListener", 303 mock(android.os.ResultReceiver.class)); 304 final ActivityOptions options = new ActivityOptions(myBundle); 305 306 final ActivityRecord testActivity = new ActivityBuilder(mAtm) 307 .setCreateTask(true) 308 .setActivityOptions(options) 309 .build(); 310 testActivity.info.applicationInfo.privateFlagsExt |= 311 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 312 final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, testActivity, 313 "window"); 314 addToWindowMap(window, true); 315 makeWindowVisibleAndDrawn(window); 316 IOnBackInvokedCallback callback = withSystemCallback(testActivity.getTask()); 317 318 BackNavigationInfo backNavigationInfo = startBackNavigation(); 319 assertTrue(testActivity.mHasSceneTransition); 320 assertThat(typeToString(backNavigationInfo.getType())) 321 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); 322 assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); 323 } 324 325 @Test testUnregisterCallbacksWithSystemCallback()326 public void testUnregisterCallbacksWithSystemCallback() 327 throws InterruptedException, RemoteException { 328 CountDownLatch systemLatch = new CountDownLatch(1); 329 CountDownLatch appLatch = new CountDownLatch(1); 330 331 final ApplicationInfo info = mock(ApplicationInfo.class); 332 final Context context = mock(Context.class); 333 Mockito.doReturn(true).when(info).isOnBackInvokedCallbackEnabled(); 334 Mockito.doReturn(info).when(context).getApplicationInfo(); 335 336 Task task = createTopTaskWithActivity(); 337 WindowState appWindow = task.getTopVisibleAppMainWindow(); 338 WindowOnBackInvokedDispatcher dispatcher = 339 new WindowOnBackInvokedDispatcher(context); 340 doAnswer(invocation -> { 341 appWindow.setOnBackInvokedCallbackInfo(invocation.getArgument(1)); 342 return null; 343 }).when(appWindow.mSession).setOnBackInvokedCallbackInfo(eq(appWindow.mClient), any()); 344 345 addToWindowMap(appWindow, true); 346 dispatcher.attachToWindow(appWindow.mSession, appWindow.mClient); 347 348 349 OnBackInvokedCallback appCallback = createBackCallback(appLatch); 350 OnBackInvokedCallback systemCallback = createBackCallback(systemLatch); 351 352 // Register both a system callback and an application callback 353 dispatcher.registerSystemOnBackInvokedCallback(systemCallback); 354 dispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, 355 appCallback); 356 357 // Check that the top callback is the app callback 358 assertEquals(appCallback, dispatcher.getTopCallback()); 359 360 // Now unregister the app callback and check that the top callback is the system callback 361 dispatcher.unregisterOnBackInvokedCallback(appCallback); 362 assertEquals(systemCallback, dispatcher.getTopCallback()); 363 364 // Verify that this has correctly been propagated to the server and that the 365 // BackNavigationInfo object will contain the system callback 366 BackNavigationInfo backNavigationInfo = startBackNavigation(); 367 assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); 368 IOnBackInvokedCallback callback = backNavigationInfo.getOnBackInvokedCallback(); 369 assertThat(callback).isNotNull(); 370 371 try { 372 callback.onBackInvoked(); 373 } catch (RemoteException e) { 374 throw new RuntimeException(e); 375 } 376 377 // Check that the system callback has been call 378 assertTrue("System callback has not been called", 379 systemLatch.await(500, TimeUnit.MILLISECONDS)); 380 assertEquals("App callback should not have been called", 381 1, appLatch.getCount()); 382 } 383 384 @Test backInfoWindowWithoutDrawn()385 public void backInfoWindowWithoutDrawn() { 386 WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_APPLICATION, 387 "TestWindow"); 388 addToWindowMap(window, true); 389 390 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 391 window.setOnBackInvokedCallbackInfo( 392 new OnBackInvokedCallbackInfo( 393 callback, 394 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 395 /* isAnimationCallback = */ false)); 396 397 BackNavigationInfo backNavigationInfo = startBackNavigation(); 398 assertThat(backNavigationInfo).isNull(); 399 } 400 401 @Test testTransitionHappensCancelNavigation()402 public void testTransitionHappensCancelNavigation() { 403 // Create a floating task and a fullscreen task, then navigating on fullscreen task. 404 // The navigation should not been cancelled when transition happens on floating task, and 405 // only be cancelled when transition happens on the navigating task. 406 final Task floatingTask = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, 407 ACTIVITY_TYPE_STANDARD); 408 final ActivityRecord baseFloatingActivity = createActivityRecord(floatingTask); 409 410 final Task fullscreenTask = createTopTaskWithActivity(); 411 withSystemCallback(fullscreenTask); 412 final ActivityRecord baseFullscreenActivity = fullscreenTask.getTopMostActivity(); 413 414 final CountDownLatch navigationObserver = new CountDownLatch(1); 415 startBackNavigation(navigationObserver); 416 417 final ArraySet<ActivityRecord> opening = new ArraySet<>(); 418 final ArraySet<ActivityRecord> closing = new ArraySet<>(); 419 final ActivityRecord secondFloatingActivity = createActivityRecord(floatingTask); 420 opening.add(secondFloatingActivity); 421 closing.add(baseFloatingActivity); 422 mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); 423 assertEquals("Transition happen on an irrelevant task, callback should not been called", 424 1, navigationObserver.getCount()); 425 426 // Create a new activity above navigation target, the transition should cancel navigation. 427 final ActivityRecord topFullscreenActivity = createActivityRecord(fullscreenTask); 428 opening.clear(); 429 closing.clear(); 430 opening.add(topFullscreenActivity); 431 closing.add(baseFullscreenActivity); 432 mBackNavigationController.removeIfContainsBackAnimationTargets(opening, closing); 433 assertEquals("Transition happen on navigation task, callback should have been called", 434 0, navigationObserver.getCount()); 435 } 436 437 @Test testWindowFocusChangeCancelNavigation()438 public void testWindowFocusChangeCancelNavigation() { 439 Task task = createTopTaskWithActivity(); 440 withSystemCallback(task); 441 WindowState focusWindow = task.getTopVisibleAppMainWindow(); 442 final CountDownLatch navigationObserver = new CountDownLatch(1); 443 startBackNavigation(navigationObserver); 444 445 mBackNavigationController.onFocusChanged(null); 446 assertEquals("change focus to null, callback should not have been called", 447 1, navigationObserver.getCount()); 448 mBackNavigationController.onFocusChanged(focusWindow); 449 assertEquals("change focus back, callback should not have been called", 450 1, navigationObserver.getCount()); 451 452 WindowState newWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlayWindow"); 453 addToWindowMap(newWindow, true); 454 mBackNavigationController.onFocusChanged(newWindow); 455 assertEquals("Focus change, callback should have been called", 456 0, navigationObserver.getCount()); 457 } 458 459 460 /** 461 * Test with 462 * config_predictShowStartingSurface = true 463 */ 464 @Test testEnableWindowlessSurface()465 public void testEnableWindowlessSurface() { 466 testPrepareAnimation(true); 467 } 468 469 /** 470 * Test with 471 * config_predictShowStartingSurface = false 472 */ 473 @Test testDisableWindowlessSurface()474 public void testDisableWindowlessSurface() { 475 testPrepareAnimation(false); 476 } 477 withSystemCallback(Task task)478 private IOnBackInvokedCallback withSystemCallback(Task task) { 479 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 480 task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( 481 new OnBackInvokedCallbackInfo( 482 callback, 483 OnBackInvokedDispatcher.PRIORITY_SYSTEM, 484 /* isAnimationCallback = */ false)); 485 return callback; 486 } 487 withAppCallback(Task task)488 private IOnBackInvokedCallback withAppCallback(Task task) { 489 IOnBackInvokedCallback callback = createOnBackInvokedCallback(); 490 task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( 491 new OnBackInvokedCallbackInfo( 492 callback, 493 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 494 /* isAnimationCallback = */ false)); 495 return callback; 496 } 497 498 @Nullable startBackNavigation()499 private BackNavigationInfo startBackNavigation() { 500 return mBackNavigationController.startBackNavigation( 501 createNavigationObserver(null), mBackAnimationAdapter); 502 } 503 504 @Nullable startBackNavigation(CountDownLatch navigationObserverLatch)505 private BackNavigationInfo startBackNavigation(CountDownLatch navigationObserverLatch) { 506 return mBackNavigationController.startBackNavigation( 507 createNavigationObserver(navigationObserverLatch), mBackAnimationAdapter); 508 } 509 510 @NonNull createOnBackInvokedCallback()511 private IOnBackInvokedCallback createOnBackInvokedCallback() { 512 return new IOnBackInvokedCallback.Stub() { 513 @Override 514 public void onBackStarted(BackMotionEvent backMotionEvent) { 515 } 516 517 @Override 518 public void onBackProgressed(BackMotionEvent backMotionEvent) { 519 } 520 521 @Override 522 public void onBackCancelled() { 523 } 524 525 @Override 526 public void onBackInvoked() { 527 } 528 }; 529 } 530 createBackCallback(CountDownLatch latch)531 private OnBackInvokedCallback createBackCallback(CountDownLatch latch) { 532 return new OnBackInvokedCallback() { 533 @Override 534 public void onBackInvoked() { 535 if (latch != null) { 536 latch.countDown(); 537 } 538 } 539 }; 540 } 541 542 private RemoteCallback createNavigationObserver(CountDownLatch latch) { 543 return new RemoteCallback(result -> { 544 if (latch != null) { 545 latch.countDown(); 546 } 547 }); 548 } 549 550 private Task initHomeActivity() { 551 final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); 552 task.forAllLeafTasks((t) -> { 553 if (t.getTopMostActivity() == null) { 554 final ActivityRecord r = createActivityRecord(t); 555 Mockito.doNothing().when(t).reparentSurfaceControl(any(), any()); 556 Mockito.doNothing().when(r).reparentSurfaceControl(any(), any()); 557 } 558 }, true); 559 return task; 560 } 561 562 private void setupKeyguardOccluded() { 563 final KeyguardController kc = mRootHomeTask.mTaskSupervisor.getKeyguardController(); 564 doReturn(true).when(kc).isKeyguardLocked(anyInt()); 565 doReturn(true).when(kc).isDisplayOccluded(anyInt()); 566 } 567 568 private void testPrepareAnimation(boolean preferWindowlessSurface) { 569 final TaskSnapshot taskSnapshot = mock(TaskSnapshot.class); 570 final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext)); 571 final Resources resourcesSpy = Mockito.spy(contextSpy.getResources()); 572 573 spyOn(mAtm.mTaskOrganizerController); 574 when(contextSpy.getResources()).thenReturn(resourcesSpy); 575 576 MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class) 577 .strictness(Strictness.LENIENT).startMocking(); 578 doReturn(taskSnapshot).when(() -> BackNavigationController.getSnapshot(any())); 579 when(resourcesSpy.getBoolean( 580 com.android.internal.R.bool.config_predictShowStartingSurface)) 581 .thenReturn(preferWindowlessSurface); 582 583 final BackNavigationController.AnimationHandler animationHandler = 584 Mockito.spy(new BackNavigationController.AnimationHandler(mWm)); 585 doReturn(true).when(animationHandler).isSupportWindowlessSurface(); 586 testWithConfig(animationHandler, preferWindowlessSurface); 587 mockitoSession.finishMocking(); 588 } 589 590 private void testWithConfig(BackNavigationController.AnimationHandler animationHandler, 591 boolean preferWindowlessSurface) { 592 final Task task = createTask(mDefaultDisplay); 593 final ActivityRecord bottomActivity = createActivityRecord(task); 594 final ActivityRecord homeActivity = mRootHomeTask.getTopNonFinishingActivity(); 595 596 final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toHomeBuilder = 597 animationHandler.prepareAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME, 598 mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity); 599 assertTrue(toHomeBuilder.mIsLaunchBehind); 600 toHomeBuilder.build(); 601 verify(mAtm.mTaskOrganizerController, never()) 602 .addWindowlessStartingSurface(any(), any(), any(), any(), any()); 603 animationHandler.clearBackAnimateTarget(); 604 605 // Back to ACTIVITY and TASK have the same logic, just with different target. 606 final ActivityRecord topActivity = createActivityRecord(task); 607 final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toActivityBuilder = 608 animationHandler.prepareAnimation( 609 BackNavigationInfo.TYPE_CROSS_ACTIVITY, mBackAnimationAdapter, task, task, 610 topActivity, bottomActivity); 611 assertFalse(toActivityBuilder.mIsLaunchBehind); 612 toActivityBuilder.build(); 613 if (preferWindowlessSurface) { 614 verify(mAtm.mTaskOrganizerController) 615 .addWindowlessStartingSurface(any(), any(), any(), any(), any()); 616 } else { 617 verify(mAtm.mTaskOrganizerController, never()) 618 .addWindowlessStartingSurface(any(), any(), any(), any(), any()); 619 } 620 } 621 622 @NonNull 623 private Task createTopTaskWithActivity() { 624 Task task = createTask(mDefaultDisplay); 625 ActivityRecord record = createActivityRecord(task); 626 // enable OnBackInvokedCallbacks 627 record.info.applicationInfo.privateFlagsExt |= 628 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 629 WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); 630 when(record.mSurfaceControl.isValid()).thenReturn(true); 631 Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); 632 mAtm.setFocusedTask(task.mTaskId, record); 633 addToWindowMap(window, true); 634 makeWindowVisibleAndDrawn(window); 635 return task; 636 } 637 638 @NonNull 639 private CrossActivityTestCase createTopTaskWithTwoActivities() { 640 Task task = createTask(mDefaultDisplay); 641 ActivityRecord record1 = createActivityRecord(task); 642 ActivityRecord record2 = createActivityRecord(task); 643 // enable OnBackInvokedCallbacks 644 record2.info.applicationInfo.privateFlagsExt |= 645 PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; 646 WindowState window1 = createWindow(null, FIRST_APPLICATION_WINDOW, record1, "window1"); 647 WindowState window2 = createWindow(null, FIRST_APPLICATION_WINDOW, record2, "window2"); 648 when(task.mSurfaceControl.isValid()).thenReturn(true); 649 when(record1.mSurfaceControl.isValid()).thenReturn(true); 650 when(record2.mSurfaceControl.isValid()).thenReturn(true); 651 Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); 652 Mockito.doNothing().when(record1).reparentSurfaceControl(any(), any()); 653 Mockito.doNothing().when(record2).reparentSurfaceControl(any(), any()); 654 mAtm.setFocusedTask(task.mTaskId, record1); 655 mAtm.setFocusedTask(task.mTaskId, record2); 656 addToWindowMap(window1, true); 657 addToWindowMap(window2, true); 658 659 makeWindowVisibleAndDrawn(window2); 660 661 CrossActivityTestCase testCase = new CrossActivityTestCase(); 662 testCase.task = task; 663 testCase.recordBack = record1; 664 testCase.recordFront = record2; 665 testCase.windowBack = window1; 666 testCase.windowFront = window2; 667 return testCase; 668 } 669 670 private void addToWindowMap(WindowState window, boolean focus) { 671 mWm.mWindowMap.put(window.mClient.asBinder(), window); 672 if (focus) { 673 doReturn(window.getWindowInfo().token) 674 .when(mWindowManagerInternal).getFocusedWindowToken(); 675 doReturn(window).when(mWm).getFocusedWindowLocked(); 676 } 677 } 678 679 private class CrossActivityTestCase { 680 public Task task; 681 public ActivityRecord recordBack; 682 public WindowState windowBack; 683 public ActivityRecord recordFront; 684 public WindowState windowFront; 685 } 686 } 687