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