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