From dee5a4dc2a5a6829e5509fc0e94903e30dcc238f Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Wed, 19 Sep 2018 11:59:40 +0900 Subject: DO NOT MERGE: Move AM/WM unit tests out of FrameworksServicesTests to WmTests Test: All presubmit and non-flaky tests pass 129 com.android.server.am 176 com.android.server.wm 32 com.android.server.policy $ tradefed.sh run commandAndExit WmTests \ --include-filter com.android.server.am. \ --include-annotation android.platform.test.annotations.Presubmit \ --exclude-annotation android.support.test.filters.FlakyTest $ tradefed.sh run commandAndExit WmTests \ --include-filter com.android.server.wm. \ --include-annotation android.platform.test.annotations.Presubmit \ --exclude-annotation android.support.test.filters.FlakyTest $ tradefed.sh run commandAndExit WmTests \ --include-filter com.android.server.policy. \ --include-annotation android.platform.test.annotations.Presubmit \ --exclude-annotation android.support.test.filters.FlakyTest Test: No AM/WM unit test in FrameworksServicesTests $ tradefed.sh run commandAndExit FrameworksServicesTests \ --include-filter com.android.server.am. $ tradefed.sh run commandAndExit FrameworksServicesTests \ --include-filter com.android.server.wm. $ tradefed.sh run commandAndExit FrameworksServicesTests \ --include-filter com.android.server.policy. Bug: 113800711 Change-Id: I01ab256aabf7e1f1b513fe3b92140f6936c2d649 --- .../am/ActivityLaunchParamsModifierTests.java | 143 --- .../server/am/ActivityManagerInternalTest.java | 214 ---- .../server/am/ActivityManagerServiceTest.java | 833 ---------------- .../com/android/server/am/ActivityManagerTest.java | 54 -- .../com/android/server/am/ActivityRecordTests.java | 258 ----- .../server/am/ActivityStackSupervisorTests.java | 410 -------- .../com/android/server/am/ActivityStackTests.java | 631 ------------ .../server/am/ActivityStartControllerTests.java | 107 -- .../server/am/ActivityStartInterceptorTest.java | 207 ---- .../android/server/am/ActivityStarterTests.java | 474 --------- .../com/android/server/am/ActivityTestsBase.java | 593 ----------- .../com/android/server/am/AppErrorDialogTest.java | 76 -- .../android/server/am/AssistDataRequesterTest.java | 374 ------- .../server/am/ClientLifecycleManagerTests.java | 44 - .../server/am/CoreSettingsObserverTest.java | 161 --- .../am/GlobalSettingsToPropertiesMapperTest.java | 112 --- .../server/am/LaunchParamsControllerTests.java | 255 ----- .../android/server/am/LockTaskControllerTest.java | 680 ------------- .../com/android/server/am/MemoryStatUtilTest.java | 162 ---- .../am/PendingRemoteAnimationRegistryTest.java | 103 -- .../server/am/PersistentConnectionTest.java | 354 ------- .../src/com/android/server/am/RecentTasksTest.java | 1026 -------------------- .../android/server/am/RecentsAnimationTest.java | 115 --- .../com/android/server/am/RunningTasksTest.java | 126 --- .../android/server/am/SafeActivityOptionsTest.java | 46 - .../server/am/TaskLaunchParamsModifierTests.java | 255 ----- .../com/android/server/am/TaskPersisterTest.java | 88 -- .../src/com/android/server/am/TaskRecordTests.java | 207 ---- .../server/am/TaskStackChangedListenerTest.java | 297 ------ .../com/android/server/am/UserControllerTest.java | 441 --------- .../com/android/server/policy/FakeWindowState.java | 266 ----- .../policy/PhoneWindowManagerInsetsTest.java | 185 ---- .../policy/PhoneWindowManagerLayoutTest.java | 367 ------- .../server/policy/PhoneWindowManagerTest.java | 253 ----- .../server/policy/PhoneWindowManagerTestBase.java | 258 ----- .../wm/AnimatingAppWindowTokenRegistryTest.java | 112 --- .../com/android/server/wm/AppTransitionTests.java | 91 -- .../wm/AppWindowContainerControllerTests.java | 250 ----- .../com/android/server/wm/AppWindowTokenTests.java | 248 ----- .../server/wm/BoundsAnimationControllerTests.java | 595 ------------ .../server/wm/ConfigurationContainerTests.java | 336 ------- .../src/com/android/server/wm/DimmerTests.java | 281 ------ .../com/android/server/wm/DisplayContentTests.java | 550 ----------- .../android/server/wm/DragDropControllerTests.java | 184 ---- .../server/wm/PinnedStackControllerTest.java | 63 -- .../server/wm/RecentsAnimationControllerTest.java | 111 --- .../server/wm/RemoteAnimationControllerTest.java | 224 ----- .../server/wm/RootWindowContainerTests.java | 52 - .../android/server/wm/ScreenDecorWindowTests.java | 351 ------- .../server/wm/StackWindowControllerTests.java | 114 --- .../server/wm/SurfaceAnimationRunnerTest.java | 236 ----- .../com/android/server/wm/SurfaceAnimatorTest.java | 295 ------ .../com/android/server/wm/TaskPositionerTests.java | 480 --------- .../server/wm/TaskPositioningControllerTests.java | 118 --- .../android/server/wm/TaskSnapshotCacheTest.java | 111 --- .../server/wm/TaskSnapshotControllerTest.java | 111 --- .../server/wm/TaskSnapshotPersisterLoaderTest.java | 303 ------ .../server/wm/TaskSnapshotPersisterTestBase.java | 141 --- .../android/server/wm/TaskSnapshotSurfaceTest.java | 246 ----- .../server/wm/TaskStackContainersTests.java | 112 --- .../src/com/android/server/wm/TaskStackTests.java | 114 --- .../wm/TaskWindowContainerControllerTests.java | 144 --- .../src/com/android/server/wm/TestIWindow.java | 116 --- .../android/server/wm/TestWindowManagerPolicy.java | 606 ------------ .../wm/UnknownAppVisibilityControllerTest.java | 88 -- .../server/wm/WallpaperControllerTests.java | 67 -- .../android/server/wm/WindowAnimationSpecTest.java | 146 --- .../server/wm/WindowConfigurationTests.java | 218 ----- .../server/wm/WindowContainerControllerTests.java | 112 --- .../android/server/wm/WindowContainerTests.java | 872 ----------------- .../server/wm/WindowContainerTraversalTests.java | 63 -- .../com/android/server/wm/WindowFrameTests.java | 474 --------- .../server/wm/WindowManagerServiceRule.java | 157 --- .../server/wm/WindowManagerServiceRuleTest.java | 42 - .../com/android/server/wm/WindowStateTests.java | 360 ------- .../android/server/wm/WindowSurfacePlacerTest.java | 76 -- .../src/com/android/server/wm/WindowTestUtils.java | 358 ------- .../src/com/android/server/wm/WindowTestsBase.java | 368 ------- .../com/android/server/wm/WindowTokenTests.java | 134 --- .../com/android/server/wm/WindowTracingTest.java | 196 ---- .../src/com/android/server/wm/ZOrderingTests.java | 399 -------- .../server/wm/utils/CoordinateTransformsTest.java | 98 -- .../android/server/wm/utils/InsetUtilsTest.java | 43 - .../android/server/wm/utils/RotationCacheTest.java | 105 -- .../server/wm/utils/WmDisplayCutoutTest.java | 157 --- services/tests/wmtests/Android.mk | 7 +- services/tests/wmtests/AndroidManifest.xml | 24 +- services/tests/wmtests/AndroidTest.xml | 6 +- .../am/ActivityLaunchParamsModifierTests.java | 143 +++ .../server/am/ActivityManagerInternalTest.java | 205 ++++ .../server/am/ActivityManagerServiceTest.java | 825 ++++++++++++++++ .../com/android/server/am/ActivityManagerTest.java | 59 ++ .../com/android/server/am/ActivityRecordTests.java | 253 +++++ .../server/am/ActivityStackSupervisorTests.java | 410 ++++++++ .../com/android/server/am/ActivityStackTests.java | 627 ++++++++++++ .../server/am/ActivityStartControllerTests.java | 107 ++ .../server/am/ActivityStartInterceptorTest.java | 207 ++++ .../android/server/am/ActivityStarterTests.java | 469 +++++++++ .../com/android/server/am/ActivityTestsBase.java | 590 +++++++++++ .../com/android/server/am/AppErrorDialogTest.java | 79 ++ .../android/server/am/AssistDataRequesterTest.java | 375 +++++++ .../server/am/ClientLifecycleManagerTests.java | 66 ++ .../server/am/CoreSettingsObserverTest.java | 151 +++ .../src/com/android/server/am/DummyAmTests.java | 46 - .../am/GlobalSettingsToPropertiesMapperTest.java | 115 +++ .../server/am/LaunchParamsControllerTests.java | 255 +++++ .../android/server/am/LockTaskControllerTest.java | 691 +++++++++++++ .../com/android/server/am/MemoryStatUtilTest.java | 168 ++++ .../am/PendingRemoteAnimationRegistryTest.java | 103 ++ .../server/am/PersistentConnectionTest.java | 361 +++++++ .../src/com/android/server/am/RecentTasksTest.java | 1024 +++++++++++++++++++ .../android/server/am/RecentsAnimationTest.java | 118 +++ .../com/android/server/am/RunningTasksTest.java | 129 +++ .../android/server/am/SafeActivityOptionsTest.java | 52 + .../server/am/TaskLaunchParamsModifierTests.java | 247 +++++ .../com/android/server/am/TaskPersisterTest.java | 84 ++ .../src/com/android/server/am/TaskRecordTests.java | 201 ++++ .../server/am/TaskStackChangedListenerTest.java | 305 ++++++ .../com/android/server/am/UserControllerTest.java | 446 +++++++++ .../com/android/server/policy/FakeWindowState.java | 266 +++++ .../policy/PhoneWindowManagerInsetsTest.java | 185 ++++ .../policy/PhoneWindowManagerLayoutTest.java | 367 +++++++ .../server/policy/PhoneWindowManagerTest.java | 253 +++++ .../server/policy/PhoneWindowManagerTestBase.java | 258 +++++ .../wm/AnimatingAppWindowTokenRegistryTest.java | 106 ++ .../com/android/server/wm/AppTransitionTests.java | 93 ++ .../wm/AppWindowContainerControllerTests.java | 252 +++++ .../com/android/server/wm/AppWindowTokenTests.java | 250 +++++ .../server/wm/BoundsAnimationControllerTests.java | 590 +++++++++++ .../server/wm/ConfigurationContainerTests.java | 337 +++++++ .../src/com/android/server/wm/DimmerTests.java | 283 ++++++ .../com/android/server/wm/DisplayContentTests.java | 551 +++++++++++ .../android/server/wm/DragDropControllerTests.java | 189 ++++ .../src/com/android/server/wm/DummyWmTests.java | 46 - .../server/wm/PinnedStackControllerTest.java | 69 ++ .../server/wm/RecentsAnimationControllerTest.java | 118 +++ .../server/wm/RemoteAnimationControllerTest.java | 226 +++++ .../server/wm/RootWindowContainerTests.java | 51 + .../android/server/wm/ScreenDecorWindowTests.java | 352 +++++++ .../server/wm/StackWindowControllerTests.java | 113 +++ .../server/wm/SurfaceAnimationRunnerTest.java | 234 +++++ .../com/android/server/wm/SurfaceAnimatorTest.java | 294 ++++++ .../com/android/server/wm/TaskPositionerTests.java | 482 +++++++++ .../server/wm/TaskPositioningControllerTests.java | 121 +++ .../android/server/wm/TaskSnapshotCacheTest.java | 113 +++ .../server/wm/TaskSnapshotControllerTest.java | 114 +++ .../server/wm/TaskSnapshotPersisterLoaderTest.java | 304 ++++++ .../server/wm/TaskSnapshotPersisterTestBase.java | 141 +++ .../android/server/wm/TaskSnapshotSurfaceTest.java | 248 +++++ .../server/wm/TaskStackContainersTests.java | 111 +++ .../src/com/android/server/wm/TaskStackTests.java | 111 +++ .../wm/TaskWindowContainerControllerTests.java | 145 +++ .../src/com/android/server/wm/TestIWindow.java | 116 +++ .../android/server/wm/TestWindowManagerPolicy.java | 606 ++++++++++++ .../wm/UnknownAppVisibilityControllerTest.java | 89 ++ .../server/wm/WallpaperControllerTests.java | 67 ++ .../android/server/wm/WindowAnimationSpecTest.java | 147 +++ .../server/wm/WindowConfigurationTests.java | 218 +++++ .../server/wm/WindowContainerControllerTests.java | 112 +++ .../android/server/wm/WindowContainerTests.java | 871 +++++++++++++++++ .../server/wm/WindowContainerTraversalTests.java | 65 ++ .../com/android/server/wm/WindowFrameTests.java | 476 +++++++++ .../server/wm/WindowManagerServiceRule.java | 157 +++ .../server/wm/WindowManagerServiceRuleTest.java | 42 + .../com/android/server/wm/WindowStateTests.java | 361 +++++++ .../android/server/wm/WindowSurfacePlacerTest.java | 83 ++ .../src/com/android/server/wm/WindowTestUtils.java | 358 +++++++ .../src/com/android/server/wm/WindowTestsBase.java | 368 +++++++ .../com/android/server/wm/WindowTokenTests.java | 134 +++ .../com/android/server/wm/WindowTracingTest.java | 195 ++++ .../src/com/android/server/wm/ZOrderingTests.java | 398 ++++++++ .../server/wm/utils/CoordinateTransformsTest.java | 105 ++ .../android/server/wm/utils/InsetUtilsTest.java | 48 + .../android/server/wm/utils/RotationCacheTest.java | 108 +++ .../server/wm/utils/WmDisplayCutoutTest.java | 157 +++ 175 files changed, 21504 insertions(+), 21501 deletions(-) delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/am/UserControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java delete mode 100644 services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/DimmerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TestIWindow.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityManagerInternalTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityManagerServiceTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityManagerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityRecordTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityStackSupervisorTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityStackTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityStartControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityStartInterceptorTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityStarterTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ActivityTestsBase.java create mode 100644 services/tests/wmtests/src/com/android/server/am/AppErrorDialogTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/AssistDataRequesterTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/ClientLifecycleManagerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/CoreSettingsObserverTest.java delete mode 100644 services/tests/wmtests/src/com/android/server/am/DummyAmTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/LaunchParamsControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/LockTaskControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/MemoryStatUtilTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/PersistentConnectionTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/RecentTasksTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/RecentsAnimationTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/RunningTasksTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/SafeActivityOptionsTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/TaskLaunchParamsModifierTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/TaskPersisterTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/TaskRecordTests.java create mode 100644 services/tests/wmtests/src/com/android/server/am/TaskStackChangedListenerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/am/UserControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/policy/FakeWindowState.java create mode 100644 services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java create mode 100644 services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java create mode 100644 services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTestBase.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/AppWindowContainerControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/DimmerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java delete mode 100644 services/tests/wmtests/src/com/android/server/wm/DummyWmTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TestIWindow.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowSurfacePlacerTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/utils/InsetUtilsTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/utils/RotationCacheTest.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java deleted file mode 100644 index f741c70e4821..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import android.app.ActivityOptions; -import android.content.pm.ActivityInfo; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.server.am.LaunchParamsController.LaunchParams; -import org.junit.runner.RunWith; -import org.junit.Before; -import org.junit.Test; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.doAnswer; - -import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; - -/** - * Tests for exercising resizing bounds due to activity options. - * - * Build/Install/Run: - * atest FrameworksServicesTests:ActivityLaunchParamsModifierTests - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ActivityLaunchParamsModifierTests extends ActivityTestsBase { - private ActivityLaunchParamsModifier mModifier; - private ActivityManagerService mService; - private ActivityStack mStack; - private TaskRecord mTask; - private ActivityRecord mActivity; - - private LaunchParams mCurrent; - private LaunchParams mResult; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - mService = createActivityManagerService(); - mModifier = new ActivityLaunchParamsModifier(mService.mStackSupervisor); - mCurrent = new LaunchParams(); - mResult = new LaunchParams(); - - - mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); - mActivity = new ActivityBuilder(mService).setTask(mTask).build(); - } - - - @Test - public void testSkippedInvocations() throws Exception { - // No specified activity should be ignored - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - null /*activity*/, null /*source*/, null /*options*/, mCurrent, mResult)); - - // No specified activity options should be ignored - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, null /*options*/, mCurrent, mResult)); - - // launch bounds specified should be ignored. - final ActivityOptions options = ActivityOptions.makeBasic(); - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - - // Non-resizeable records should be ignored - mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - assertFalse(mActivity.isResizeable()); - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - - // make record resizeable - mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; - assertTrue(mActivity.isResizeable()); - - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - - // Does not support freeform - mService.mSupportsFreeformWindowManagement = false; - assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - - mService.mSupportsFreeformWindowManagement = true; - options.setLaunchBounds(new Rect()); - assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); - - // Invalid bounds - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - options.setLaunchBounds(new Rect(0, 0, -1, -1)); - assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - - // Valid bounds should cause the positioner to be applied. - options.setLaunchBounds(new Rect(0, 0, 100, 100)); - assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - } - - @Test - public void testBoundsExtraction() throws Exception { - // Make activity resizeable and enable freeform mode. - mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; - mService.mSupportsFreeformWindowManagement = true; - - ActivityOptions options = ActivityOptions.makeBasic(); - final Rect proposedBounds = new Rect(20, 30, 45, 40); - options.setLaunchBounds(proposedBounds); - - assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/, - mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); - assertEquals(mResult.mBounds, proposedBounds); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java deleted file mode 100644 index bce87dc58819..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManagerInternal; -import android.os.SystemClock; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Test class for {@link ActivityManagerInternal}. - * - * To run the tests, use - * - * runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services - * - * or the following steps: - * - * Build: m FrameworksServicesTests - * Install: adb install -r \ - * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \ - * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner - */ -@RunWith(AndroidJUnit4.class) -public class ActivityManagerInternalTest { - private static final int TEST_UID1 = 111; - private static final int TEST_UID2 = 112; - - private static final long TEST_PROC_STATE_SEQ1 = 1111; - private static final long TEST_PROC_STATE_SEQ2 = 1112; - private static final long TEST_PROC_STATE_SEQ3 = 1113; - - @Mock private ActivityManagerService.Injector mMockInjector; - - private ActivityManagerService mAms; - private ActivityManagerInternal mAmi; - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mAms = new ActivityManagerService(mMockInjector); - mAmi = mAms.new LocalService(); - } - - @MediumTest - @Test - public void testNotifyNetworkPolicyRulesUpdated() throws Exception { - // Check there is no crash when there are no active uid records. - mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, TEST_PROC_STATE_SEQ1); - - // Notify that network policy rules are updated for TEST_UID1 and verify that - // UidRecord.lastNetworkUpdateProcStateSeq is updated and any blocked threads are notified. - verifyNetworkUpdatedProcStateSeq( - TEST_PROC_STATE_SEQ2, // curProcStateSeq - TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq - TEST_PROC_STATE_SEQ2, // procStateSeq to notify - true); // expectNotify - - // Notify that network policy rules are updated for TEST_UID1 with already handled - // procStateSeq and verify that there is no notify call. - verifyNetworkUpdatedProcStateSeq( - TEST_PROC_STATE_SEQ1, // curProcStateSeq - TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq - TEST_PROC_STATE_SEQ1, // procStateSeq to notify - false); // expectNotify - - // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older - // than it's UidRecord.curProcStateSeq and verify that there is no notify call. - verifyNetworkUpdatedProcStateSeq( - TEST_PROC_STATE_SEQ3, // curProcStateSeq - TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq - TEST_PROC_STATE_SEQ2, // procStateSeq to notify - false); // expectNotify - } - - private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq, - long lastNetworkUpdatedProcStateSeq, long expectedProcStateSeq, boolean expectNotify) - throws Exception { - final UidRecord record1 = addActiveUidRecord(TEST_UID1, curProcStateSeq, - lastNetworkUpdatedProcStateSeq); - final UidRecord record2 = addActiveUidRecord(TEST_UID2, curProcStateSeq, - lastNetworkUpdatedProcStateSeq); - - final CustomThread thread1 = new CustomThread(record1.networkStateLock); - thread1.startAndWait("Unexpected state for " + record1); - final CustomThread thread2 = new CustomThread(record2.networkStateLock); - thread2.startAndWait("Unexpected state for " + record2); - - mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, expectedProcStateSeq); - assertEquals(record1 + " should be updated", - expectedProcStateSeq, record1.lastNetworkUpdatedProcStateSeq); - assertEquals(record2 + " should not be updated", - lastNetworkUpdatedProcStateSeq, record2.lastNetworkUpdatedProcStateSeq); - - if (expectNotify) { - thread1.assertTerminated("Unexpected state for " + record1); - assertTrue("Threads waiting for network should be notified: " + record1, - thread1.mNotified); - } else { - thread1.assertWaiting("Unexpected state for " + record1); - thread1.interrupt(); - } - thread2.assertWaiting("Unexpected state for " + record2); - thread2.interrupt(); - - mAms.mActiveUids.clear(); - } - - private UidRecord addActiveUidRecord(int uid, long curProcStateSeq, - long lastNetworkUpdatedProcStateSeq) { - final UidRecord record = new UidRecord(uid); - record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; - record.curProcStateSeq = curProcStateSeq; - record.waitingForNetwork = true; - mAms.mActiveUids.put(uid, record); - return record; - } - - static class CustomThread extends Thread { - private static final long WAIT_TIMEOUT_MS = 1000; - private static final long WAIT_INTERVAL_MS = 100; - - private final Object mLock; - private Runnable mRunnable; - boolean mNotified; - - public CustomThread(Object lock) { - mLock = lock; - } - - public CustomThread(Object lock, Runnable runnable) { - super(runnable); - mLock = lock; - mRunnable = runnable; - } - - @Override - public void run() { - if (mRunnable != null) { - mRunnable.run(); - } else { - synchronized (mLock) { - try { - mLock.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupted(); - } - } - } - mNotified = !Thread.interrupted(); - } - - public void startAndWait(String errMsg) throws Exception { - startAndWait(errMsg, false); - } - - public void startAndWait(String errMsg, boolean timedWaiting) throws Exception { - start(); - final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS; - final Thread.State stateToReach = timedWaiting - ? Thread.State.TIMED_WAITING : Thread.State.WAITING; - while (getState() != stateToReach - && SystemClock.elapsedRealtime() < endTime) { - Thread.sleep(WAIT_INTERVAL_MS); - } - if (timedWaiting) { - assertTimedWaiting(errMsg); - } else { - assertWaiting(errMsg); - } - } - - public void assertWaiting(String errMsg) { - assertEquals(errMsg, Thread.State.WAITING, getState()); - } - - public void assertTimedWaiting(String errMsg) { - assertEquals(errMsg, Thread.State.TIMED_WAITING, getState()); - } - - public void assertTerminated(String errMsg) throws Exception { - final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS; - while (getState() != Thread.State.TERMINATED - && SystemClock.elapsedRealtime() < endTime) { - Thread.sleep(WAIT_INTERVAL_MS); - } - assertEquals(errMsg, Thread.State.TERMINATED, getState()); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java deleted file mode 100644 index c70d1e18c871..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ /dev/null @@ -1,833 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; -import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; -import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; -import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; -import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; -import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; -import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; -import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; -import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; -import static android.app.ActivityManager.PROCESS_STATE_SERVICE; -import static android.app.ActivityManager.PROCESS_STATE_TOP; -import static android.util.DebugUtils.valueToString; -import static com.android.server.am.ActivityManagerInternalTest.CustomThread; -import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG; -import static com.android.server.am.ActivityManagerService.Injector; -import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK; -import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE; -import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.app.IApplicationThread; -import android.app.IUidObserver; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemClock; -import android.support.test.filters.MediumTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.internal.os.BatteryStatsImpl; -import com.android.server.AppOpsService; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -/** - * Test class for {@link ActivityManagerService}. - * - * To run the tests, use - * - * runtest -c com.android.server.am.ActivityManagerServiceTest frameworks-services - * - * or the following steps: - * - * Build: m FrameworksServicesTests - * Install: adb install -r \ - * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerServiceTest -w \ - * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ActivityManagerServiceTest { - private static final String TAG = ActivityManagerServiceTest.class.getSimpleName(); - - private static final int TEST_UID = 11111; - - private static final long TEST_PROC_STATE_SEQ1 = 555; - private static final long TEST_PROC_STATE_SEQ2 = 556; - - private static final int[] UID_RECORD_CHANGES = { - UidRecord.CHANGE_PROCSTATE, - UidRecord.CHANGE_GONE, - UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE, - UidRecord.CHANGE_IDLE, - UidRecord.CHANGE_ACTIVE - }; - - @Mock private Context mContext; - @Mock private AppOpsService mAppOpsService; - @Mock private PackageManager mPackageManager; - @Mock private BatteryStatsImpl mBatteryStatsImpl; - - private TestInjector mInjector; - private ActivityManagerService mAms; - private HandlerThread mHandlerThread; - private TestHandler mHandler; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mHandlerThread = new HandlerThread(TAG); - mHandlerThread.start(); - mHandler = new TestHandler(mHandlerThread.getLooper()); - mInjector = new TestInjector(); - mAms = new ActivityManagerService(mInjector); - mAms.mWaitForNetworkTimeoutMs = 2000; - - when(mContext.getPackageManager()).thenReturn(mPackageManager); - } - - @After - public void tearDown() { - mHandlerThread.quit(); - } - - @MediumTest - @Test - public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception { - - final UidRecord uidRec = addUidRecord(TEST_UID); - addUidRecord(TEST_UID + 1); - - // Uid state is not moving from background to foreground or vice versa. - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_TOP, // prevState - PROCESS_STATE_TOP, // curState - 0, // expectedGlobalCounter - 0, // exptectedCurProcStateSeq - NETWORK_STATE_NO_CHANGE, // expectedBlockState - false); // expectNotify - - // Uid state is moving from foreground to background. - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_FOREGROUND_SERVICE, // prevState - PROCESS_STATE_SERVICE, // curState - 1, // expectedGlobalCounter - 1, // exptectedCurProcStateSeq - NETWORK_STATE_UNBLOCK, // expectedBlockState - true); // expectNotify - - // Explicitly setting the seq counter for more verification. - mAms.mProcStateSeqCounter = 42; - - // Uid state is not moving from background to foreground or vice versa. - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState - PROCESS_STATE_IMPORTANT_FOREGROUND, // curState - 42, // expectedGlobalCounter - 1, // exptectedCurProcStateSeq - NETWORK_STATE_NO_CHANGE, // expectedBlockState - false); // expectNotify - - // Uid state is moving from background to foreground. - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_LAST_ACTIVITY, // prevState - PROCESS_STATE_TOP, // curState - 43, // expectedGlobalCounter - 43, // exptectedCurProcStateSeq - NETWORK_STATE_BLOCK, // expectedBlockState - false); // expectNotify - - // verify waiting threads are not notified. - uidRec.waitingForNetwork = false; - // Uid state is moving from foreground to background. - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_FOREGROUND_SERVICE, // prevState - PROCESS_STATE_SERVICE, // curState - 44, // expectedGlobalCounter - 44, // exptectedCurProcStateSeq - NETWORK_STATE_UNBLOCK, // expectedBlockState - false); // expectNotify - - // Verify when uid is not restricted, procStateSeq is not incremented. - uidRec.waitingForNetwork = true; - mInjector.setNetworkRestrictedForUid(false); - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState - PROCESS_STATE_TOP, // curState - 44, // expectedGlobalCounter - 44, // exptectedCurProcStateSeq - -1, // expectedBlockState, -1 to verify there are no interactions with main thread. - false); // expectNotify - - // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented. - mAms.mWaitForNetworkTimeoutMs = 0; - mInjector.setNetworkRestrictedForUid(true); - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_TOP, // prevState - PROCESS_STATE_IMPORTANT_BACKGROUND, // curState - 44, // expectedGlobalCounter - 44, // exptectedCurProcStateSeq - -1, // expectedBlockState, -1 to verify there are no interactions with main thread. - false); // expectNotify - - // Verify when the uid doesn't have internet permission, then procStateSeq is not - // incremented. - uidRec.hasInternetPermission = false; - mAms.mWaitForNetworkTimeoutMs = 111; - mInjector.setNetworkRestrictedForUid(true); - verifySeqCounterAndInteractions(uidRec, - PROCESS_STATE_CACHED_ACTIVITY, // prevState - PROCESS_STATE_FOREGROUND_SERVICE, // curState - 44, // expectedGlobalCounter - 44, // exptectedCurProcStateSeq - -1, // expectedBlockState, -1 to verify there are no interactions with main thread. - false); // expectNotify - - // Verify procStateSeq is not incremented when the uid is not an application, regardless - // of the process state. - final int notAppUid = 111; - final UidRecord uidRec2 = addUidRecord(notAppUid); - verifySeqCounterAndInteractions(uidRec2, - PROCESS_STATE_CACHED_EMPTY, // prevState - PROCESS_STATE_TOP, // curState - 44, // expectedGlobalCounter - 0, // exptectedCurProcStateSeq - -1, // expectedBlockState, -1 to verify there are no interactions with main thread. - false); // expectNotify - } - - private UidRecord addUidRecord(int uid) { - final UidRecord uidRec = new UidRecord(uid); - uidRec.waitingForNetwork = true; - uidRec.hasInternetPermission = true; - mAms.mActiveUids.put(uid, uidRec); - - final ProcessRecord appRec = new ProcessRecord(null, mBatteryStatsImpl, - new ApplicationInfo(), TAG, uid); - appRec.thread = Mockito.mock(IApplicationThread.class); - mAms.mLruProcesses.add(appRec); - - return uidRec; - } - - private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState, - int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState, - boolean expectNotify) throws Exception { - CustomThread thread = new CustomThread(uidRec.networkStateLock); - thread.startAndWait("Unexpected state for " + uidRec); - - uidRec.setProcState = prevState; - uidRec.curProcState = curState; - mAms.incrementProcStateSeqAndNotifyAppsLocked(); - - assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter); - assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq); - - for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) { - final ProcessRecord app = mAms.mLruProcesses.get(i); - // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE. - if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) { - verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq); - } else { - verifyZeroInteractions(app.thread); - } - Mockito.reset(app.thread); - } - - if (expectNotify) { - thread.assertTerminated("Unexpected state for " + uidRec); - } else { - thread.assertWaiting("Unexpected state for " + uidRec); - thread.interrupt(); - } - } - - @Test - public void testBlockStateForUid() { - final UidRecord uidRec = new UidRecord(TEST_UID); - int expectedBlockState; - - final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s"; - Function errorMsg = (blockState) -> { - return String.format(errorTemplate, - valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState), - valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState), - valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState)); - }; - - // No change in uid state - uidRec.setProcState = PROCESS_STATE_RECEIVER; - uidRec.curProcState = PROCESS_STATE_RECEIVER; - expectedBlockState = NETWORK_STATE_NO_CHANGE; - assertEquals(errorMsg.apply(expectedBlockState), - expectedBlockState, mAms.getBlockStateForUid(uidRec)); - - // Foreground to foreground - uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE; - uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - expectedBlockState = NETWORK_STATE_NO_CHANGE; - assertEquals(errorMsg.apply(expectedBlockState), - expectedBlockState, mAms.getBlockStateForUid(uidRec)); - - // Background to background - uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY; - uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY; - expectedBlockState = NETWORK_STATE_NO_CHANGE; - assertEquals(errorMsg.apply(expectedBlockState), - expectedBlockState, mAms.getBlockStateForUid(uidRec)); - - // Background to background - uidRec.setProcState = PROCESS_STATE_NONEXISTENT; - uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY; - expectedBlockState = NETWORK_STATE_NO_CHANGE; - assertEquals(errorMsg.apply(expectedBlockState), - expectedBlockState, mAms.getBlockStateForUid(uidRec)); - - // Background to foreground - uidRec.setProcState = PROCESS_STATE_SERVICE; - uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE; - expectedBlockState = NETWORK_STATE_BLOCK; - assertEquals(errorMsg.apply(expectedBlockState), - expectedBlockState, mAms.getBlockStateForUid(uidRec)); - - // Foreground to background - uidRec.setProcState = PROCESS_STATE_TOP; - uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY; - expectedBlockState = NETWORK_STATE_UNBLOCK; - assertEquals(errorMsg.apply(expectedBlockState), - expectedBlockState, mAms.getBlockStateForUid(uidRec)); - } - - /** - * This test verifies that process state changes are dispatched to observers based on the - * changes they wanted to listen (this is specified when registering the observer). - */ - @Test - public void testDispatchUids_dispatchNeededChanges() throws RemoteException { - when(mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null)) - .thenReturn(AppOpsManager.MODE_ALLOWED); - - final int[] changesToObserve = { - ActivityManager.UID_OBSERVER_PROCSTATE, - ActivityManager.UID_OBSERVER_GONE, - ActivityManager.UID_OBSERVER_IDLE, - ActivityManager.UID_OBSERVER_ACTIVE, - ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE - | ActivityManager.UID_OBSERVER_ACTIVE | ActivityManager.UID_OBSERVER_IDLE - }; - final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length]; - for (int i = 0; i < observers.length; ++i) { - observers[i] = Mockito.mock(IUidObserver.Stub.class); - when(observers[i].asBinder()).thenReturn((IBinder) observers[i]); - mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */, - ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */); - - // When we invoke AMS.registerUidObserver, there are some interactions with observers[i] - // mock in RemoteCallbackList class. We don't want to test those interactions and - // at the same time, we don't want those to interfere with verifyNoMoreInteractions. - // So, resetting the mock here. - Mockito.reset(observers[i]); - } - - // Add pending uid records each corresponding to a different change type UidRecord.CHANGE_* - final int[] changesForPendingUidRecords = UID_RECORD_CHANGES; - - final int[] procStatesForPendingUidRecords = { - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_STATE_NONEXISTENT, - ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, - ActivityManager.PROCESS_STATE_TOP - }; - final Map changeItems = new HashMap<>(); - for (int i = 0; i < changesForPendingUidRecords.length; ++i) { - final UidRecord.ChangeItem pendingChange = new UidRecord.ChangeItem(); - pendingChange.change = changesForPendingUidRecords[i]; - pendingChange.uid = i; - pendingChange.processState = procStatesForPendingUidRecords[i]; - pendingChange.procStateSeq = i; - changeItems.put(changesForPendingUidRecords[i], pendingChange); - mAms.mPendingUidChanges.add(pendingChange); - } - - mAms.dispatchUidsChanged(); - // Verify the required changes have been dispatched to observers. - for (int i = 0; i < observers.length; ++i) { - final int changeToObserve = changesToObserve[i]; - final IUidObserver observerToTest = observers[i]; - if ((changeToObserve & ActivityManager.UID_OBSERVER_IDLE) != 0) { - // Observer listens to uid idle changes, so change items corresponding to - // UidRecord.CHANGE_IDLE or UidRecord.CHANGE_IDLE_GONE needs to be - // delivered to this observer. - final int[] changesToVerify = { - UidRecord.CHANGE_IDLE, - UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE - }; - verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, - (observer, changeItem) -> { - verify(observer).onUidIdle(changeItem.uid, changeItem.ephemeral); - }); - } - if ((changeToObserve & ActivityManager.UID_OBSERVER_ACTIVE) != 0) { - // Observer listens to uid active changes, so change items corresponding to - // UidRecord.CHANGE_ACTIVE needs to be delivered to this observer. - final int[] changesToVerify = { UidRecord.CHANGE_ACTIVE }; - verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, - (observer, changeItem) -> { - verify(observer).onUidActive(changeItem.uid); - }); - } - if ((changeToObserve & ActivityManager.UID_OBSERVER_GONE) != 0) { - // Observer listens to uid gone changes, so change items corresponding to - // UidRecord.CHANGE_GONE or UidRecord.CHANGE_IDLE_GONE needs to be - // delivered to this observer. - final int[] changesToVerify = { - UidRecord.CHANGE_GONE, - UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE - }; - verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, - (observer, changeItem) -> { - verify(observer).onUidGone(changeItem.uid, changeItem.ephemeral); - }); - } - if ((changeToObserve & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { - // Observer listens to uid procState changes, so change items corresponding to - // UidRecord.CHANGE_PROCSTATE or UidRecord.CHANGE_IDLE or UidRecord.CHANGE_ACTIVE - // needs to be delivered to this observer. - final int[] changesToVerify = { - UidRecord.CHANGE_PROCSTATE, - UidRecord.CHANGE_ACTIVE, - UidRecord.CHANGE_IDLE - }; - verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, - (observer, changeItem) -> { - verify(observer).onUidStateChanged(changeItem.uid, - changeItem.processState, changeItem.procStateSeq); - }); - } - // Verify there are no other callbacks for this observer. - verifyNoMoreInteractions(observerToTest); - } - } - - private interface ObserverChangesVerifier { - void verify(IUidObserver observer, UidRecord.ChangeItem changeItem) throws RemoteException; - } - - private void verifyObserverReceivedChanges(IUidObserver observer, int[] changesToVerify, - Map changeItems, ObserverChangesVerifier verifier) - throws RemoteException { - for (int change : changesToVerify) { - final UidRecord.ChangeItem changeItem = changeItems.get(change); - verifier.verify(observer, changeItem); - } - } - - /** - * This test verifies that process state changes are dispatched to observers only when they - * change across the cutpoint (this is specified when registering the observer). - */ - @Test - public void testDispatchUidChanges_procStateCutpoint() throws RemoteException { - final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class); - - when(observer.asBinder()).thenReturn((IBinder) observer); - mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */, - ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */); - // When we invoke AMS.registerUidObserver, there are some interactions with observer - // mock in RemoteCallbackList class. We don't want to test those interactions and - // at the same time, we don't want those to interfere with verifyNoMoreInteractions. - // So, resetting the mock here. - Mockito.reset(observer); - - final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem(); - changeItem.uid = TEST_UID; - changeItem.change = UidRecord.CHANGE_PROCSTATE; - changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; - changeItem.procStateSeq = 111; - mAms.mPendingUidChanges.add(changeItem); - mAms.dispatchUidsChanged(); - // First process state message is always delivered regardless of whether the process state - // change is above or below the cutpoint (PROCESS_STATE_SERVICE). - verify(observer).onUidStateChanged(TEST_UID, - changeItem.processState, changeItem.procStateSeq); - verifyNoMoreInteractions(observer); - - changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER; - mAms.mPendingUidChanges.add(changeItem); - mAms.dispatchUidsChanged(); - // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and - // the current process state change is also below cutpoint, so no callback will be invoked. - verifyNoMoreInteractions(observer); - - changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; - mAms.mPendingUidChanges.add(changeItem); - mAms.dispatchUidsChanged(); - // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and - // the current process state change is above cutpoint, so callback will be invoked with the - // current process state change. - verify(observer).onUidStateChanged(TEST_UID, - changeItem.processState, changeItem.procStateSeq); - verifyNoMoreInteractions(observer); - - changeItem.processState = ActivityManager.PROCESS_STATE_TOP; - mAms.mPendingUidChanges.add(changeItem); - mAms.dispatchUidsChanged(); - // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and - // the current process state change is also above cutpoint, so no callback will be invoked. - verifyNoMoreInteractions(observer); - - changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; - mAms.mPendingUidChanges.add(changeItem); - mAms.dispatchUidsChanged(); - // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and - // the current process state change is below cutpoint, so callback will be invoked with the - // current process state change. - verify(observer).onUidStateChanged(TEST_UID, - changeItem.processState, changeItem.procStateSeq); - verifyNoMoreInteractions(observer); - } - - /** - * This test verifies that {@link ActivityManagerService#mValidateUids} which is a - * part of dumpsys is correctly updated. - */ - @Test - public void testDispatchUidChanges_validateUidsUpdated() { - final int[] changesForPendingItems = UID_RECORD_CHANGES; - - final int[] procStatesForPendingItems = { - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, - ActivityManager.PROCESS_STATE_CACHED_EMPTY, - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, - ActivityManager.PROCESS_STATE_SERVICE, - ActivityManager.PROCESS_STATE_RECEIVER - }; - final ArrayList pendingItemsForUids - = new ArrayList<>(changesForPendingItems.length); - for (int i = 0; i < changesForPendingItems.length; ++i) { - final UidRecord.ChangeItem item = new UidRecord.ChangeItem(); - item.uid = i; - item.change = changesForPendingItems[i]; - item.processState = procStatesForPendingItems[i]; - pendingItemsForUids.add(i, item); - } - - // Verify that when there no observers listening to uid state changes, then there will - // be no changes to validateUids. - mAms.mPendingUidChanges.addAll(pendingItemsForUids); - mAms.dispatchUidsChanged(); - assertEquals("No observers registered, so validateUids should be empty", - 0, mAms.mValidateUids.size()); - - final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class); - when(observer.asBinder()).thenReturn((IBinder) observer); - mAms.registerUidObserver(observer, 0, 0, null); - // Verify that when observers are registered, then validateUids is correctly updated. - mAms.mPendingUidChanges.addAll(pendingItemsForUids); - mAms.dispatchUidsChanged(); - for (int i = 0; i < pendingItemsForUids.size(); ++i) { - final UidRecord.ChangeItem item = pendingItemsForUids.get(i); - final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid); - if ((item.change & UidRecord.CHANGE_GONE) != 0) { - assertNull("validateUidRecord should be null since the change is either " - + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord); - } else { - assertNotNull("validateUidRecord should not be null since the change is neither " - + "CHANGE_GONE nor CHANGE_GONE_IDLE", validateUidRecord); - assertEquals("processState: " + item.processState + " curProcState: " - + validateUidRecord.curProcState + " should have been equal", - item.processState, validateUidRecord.curProcState); - assertEquals("processState: " + item.processState + " setProcState: " - + validateUidRecord.curProcState + " should have been equal", - item.processState, validateUidRecord.setProcState); - if (item.change == UidRecord.CHANGE_IDLE) { - assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE", - validateUidRecord.idle); - } else if (item.change == UidRecord.CHANGE_ACTIVE) { - assertFalse("UidRecord.idle should be updated to false for CHANGE_ACTIVE", - validateUidRecord.idle); - } - } - } - - // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it - // will be removed from validateUids. - assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size()); - for (int i = 0; i < pendingItemsForUids.size(); ++i) { - final UidRecord.ChangeItem item = pendingItemsForUids.get(i); - // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd - // distribution for this assignment. - item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE) - : UidRecord.CHANGE_GONE; - } - mAms.mPendingUidChanges.addAll(pendingItemsForUids); - mAms.dispatchUidsChanged(); - assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids, - 0, mAms.mValidateUids.size()); - } - - @Test - public void testEnqueueUidChangeLocked_procStateSeqUpdated() { - final UidRecord uidRecord = new UidRecord(TEST_UID); - uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1; - - // Verify with no pending changes for TEST_UID. - verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1); - - // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as - // expected. - final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem(); - uidRecord.pendingChange = changeItem; - uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2; - verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2); - } - - @Test - public void testEnqueueUidChangeLocked_nullUidRecord() { - // Use "null" uidRecord to make sure there is no crash. - mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE); - } - - private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) { - // Test enqueueUidChangeLocked with every UidRecord.CHANGE_* - for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) { - final int changeToDispatch = UID_RECORD_CHANGES[i]; - // Reset lastProcStateSeqDispatchToObservers after every test. - uidRecord.lastDispatchedProcStateSeq = 0; - mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch); - // Verify there is no effect on curProcStateSeq. - assertEquals(curProcstateSeq, uidRecord.curProcStateSeq); - if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) { - // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that - // lastProcStateSeqDispatchedToObservers is not updated. - assertNotEquals(uidRecord.curProcStateSeq, - uidRecord.lastDispatchedProcStateSeq); - } else { - // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that - // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq. - assertEquals(uidRecord.curProcStateSeq, - uidRecord.lastDispatchedProcStateSeq); - } - } - } - - @MediumTest - @Test - public void testEnqueueUidChangeLocked_dispatchUidsChanged() { - final UidRecord uidRecord = new UidRecord(TEST_UID); - final int expectedProcState = PROCESS_STATE_SERVICE; - uidRecord.setProcState = expectedProcState; - uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1; - - // Test with no pending uid records. - for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) { - final int changeToDispatch = UID_RECORD_CHANGES[i]; - - // Reset the current state - mHandler.reset(); - uidRecord.pendingChange = null; - mAms.mPendingUidChanges.clear(); - - mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch); - - // Verify that UidRecord.pendingChange is updated correctly. - assertNotNull(uidRecord.pendingChange); - assertEquals(TEST_UID, uidRecord.pendingChange.uid); - assertEquals(expectedProcState, uidRecord.pendingChange.processState); - assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq); - - // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler. - mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG); - } - } - - @MediumTest - @Test - public void testWaitForNetworkStateUpdate() throws Exception { - // Check there is no crash when there is no UidRecord for myUid - mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1); - - // Verify there is no waiting when UidRecord.curProcStateSeq is greater than - // the procStateSeq in the request to wait. - verifyWaitingForNetworkStateUpdate( - TEST_PROC_STATE_SEQ1, // curProcStateSeq - TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq - TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq - TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait - false); // expectWait - - // Verify there is no waiting when the procStateSeq in the request to wait is - // not dispatched to NPMS. - verifyWaitingForNetworkStateUpdate( - TEST_PROC_STATE_SEQ1, // curProcStateSeq - TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq - TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq - TEST_PROC_STATE_SEQ1, // procStateSeqToWait - false); // expectWait - - // Verify there is not waiting when the procStateSeq in the request already has - // an updated network state. - verifyWaitingForNetworkStateUpdate( - TEST_PROC_STATE_SEQ1, // curProcStateSeq - TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq - TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq - TEST_PROC_STATE_SEQ1, // procStateSeqToWait - false); // expectWait - - // Verify waiting for network works - verifyWaitingForNetworkStateUpdate( - TEST_PROC_STATE_SEQ1, // curProcStateSeq - TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq - TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq - TEST_PROC_STATE_SEQ1, // procStateSeqToWait - true); // expectWait - } - - private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq, - long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq, - final long procStateSeqToWait, boolean expectWait) throws Exception { - final UidRecord record = new UidRecord(Process.myUid()); - record.curProcStateSeq = curProcStateSeq; - record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq; - record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; - mAms.mActiveUids.put(Process.myUid(), record); - - CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() { - @Override - public void run() { - mAms.waitForNetworkStateUpdate(procStateSeqToWait); - } - }); - final String errMsg = "Unexpected state for " + record; - if (expectWait) { - thread.startAndWait(errMsg, true); - thread.assertTimedWaiting(errMsg); - synchronized (record.networkStateLock) { - record.networkStateLock.notifyAll(); - } - thread.assertTerminated(errMsg); - assertTrue(thread.mNotified); - assertFalse(record.waitingForNetwork); - } else { - thread.start(); - thread.assertTerminated(errMsg); - } - - mAms.mActiveUids.clear(); - } - - private class TestHandler extends Handler { - private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec - private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec - - private Set mMsgsHandled = new HashSet<>(); - - TestHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - mMsgsHandled.add(msg.what); - } - - public void waitForMessage(int msg) { - final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS; - while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) { - SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS); - } - if (!mMsgsHandled.contains(msg)) { - fail("Timed out waiting for the message to be handled, msg: " + msg); - } - } - - public void reset() { - mMsgsHandled.clear(); - } - } - - private class TestInjector extends Injector { - private boolean mRestricted = true; - - @Override - public Context getContext() { - return mContext; - } - - @Override - public AppOpsService getAppOpsService(File file, Handler handler) { - return mAppOpsService; - } - - @Override - public Handler getUiHandler(ActivityManagerService service) { - return mHandler; - } - - @Override - public boolean isNetworkRestrictedForUid(int uid) { - return mRestricted; - } - - public void setNetworkRestrictedForUid(boolean restricted) { - mRestricted = restricted; - } - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java deleted file mode 100644 index ba25b1659bd2..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import android.app.ActivityManager; -import android.app.IActivityManager; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.os.RemoteException; -import android.test.AndroidTestCase; - -import java.util.List; - -public class ActivityManagerTest extends AndroidTestCase { - - IActivityManager service; - @Override - public void setUp() throws Exception { - super.setUp(); - service = ActivityManager.getService(); - } - - public void testTaskIdsForRunningUsers() throws RemoteException { - for(int userId : service.getRunningUserIds()) { - testTaskIdsForUser(userId); - } - } - - private void testTaskIdsForUser(int userId) throws RemoteException { - List recentTasks = service.getRecentTasks( - 100, 0, userId).getList(); - if(recentTasks != null) { - for(ActivityManager.RecentTaskInfo recentTask : recentTasks) { - int taskId = recentTask.persistentId; - assertEquals("The task id " + taskId + " should not belong to user " + userId, - taskId / UserHandle.PER_USER_RANGE, userId); - } - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java deleted file mode 100644 index 5ee1c405bb0e..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; -import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; -import static com.android.server.am.ActivityStack.ActivityState.FINISHING; -import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; -import static com.android.server.am.ActivityStack.ActivityState.PAUSED; -import static com.android.server.am.ActivityStack.ActivityState.PAUSING; -import static com.android.server.am.ActivityStack.ActivityState.STOPPED; -import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; - -import static junit.framework.TestCase.assertNotNull; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityOptions; -import android.app.servertransaction.ClientTransaction; -import android.app.servertransaction.PauseActivityItem; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.MutableBoolean; - -import org.junit.runner.RunWith; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; - -/** - * Tests for the {@link ActivityRecord} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.am.ActivityRecordTests - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ActivityRecordTests extends ActivityTestsBase { - private ActivityManagerService mService; - private TestActivityStack mStack; - private TaskRecord mTask; - private ActivityRecord mActivity; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mService = createActivityManagerService(); - mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); - mActivity = new ActivityBuilder(mService).setTask(mTask).build(); - } - - @Test - public void testStackCleanupOnClearingTask() throws Exception { - mActivity.setTask(null); - assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); - } - - @Test - public void testStackCleanupOnActivityRemoval() throws Exception { - mTask.removeActivity(mActivity); - assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); - } - - @Test - public void testStackCleanupOnTaskRemoval() throws Exception { - mStack.removeTask(mTask, null /*reason*/, REMOVE_TASK_MODE_MOVING); - // Stack should be gone on task removal. - assertNull(mService.mStackSupervisor.getStack(mStack.mStackId)); - } - - @Test - public void testNoCleanupMovingActivityInSameStack() throws Exception { - final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack) - .build(); - mActivity.reparent(newTask, 0, null /*reason*/); - assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0); - } - - @Test - public void testPausingWhenVisibleFromStopped() throws Exception { - final MutableBoolean pauseFound = new MutableBoolean(false); - doAnswer((InvocationOnMock invocationOnMock) -> { - final ClientTransaction transaction = invocationOnMock.getArgument(0); - if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) { - pauseFound.value = true; - } - return null; - }).when(mActivity.app.thread).scheduleTransaction(any()); - - mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); - - // The activity is in the focused stack so it should not move to paused. - mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - assertTrue(mActivity.isState(STOPPED)); - assertFalse(pauseFound.value); - - // Clear focused stack - mActivity.mStackSupervisor.mFocusedStack = null; - - // In the unfocused stack, the activity should move to paused. - mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - assertTrue(mActivity.isState(PAUSING)); - assertTrue(pauseFound.value); - - // Make sure that the state does not change for current non-stopping states. - mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); - - mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - - assertTrue(mActivity.isState(INITIALIZING)); - - // Make sure the state does not change if we are not the current top activity. - mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind"); - - // Make sure that the state does not change when we have an activity becoming translucent - final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - mStack.mTranslucentActivityWaiting = topActivity; - mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); - - assertTrue(mActivity.isState(STOPPED)); - } - - @Test - public void testPositionLimitedAspectRatioNavBarBottom() throws Exception { - verifyPositionWithLimitedAspectRatio(NAV_BAR_BOTTOM, new Rect(0, 0, 1000, 2000), 1.5f, - new Rect(0, 0, 1000, 1500)); - } - - @Test - public void testPositionLimitedAspectRatioNavBarLeft() throws Exception { - verifyPositionWithLimitedAspectRatio(NAV_BAR_LEFT, new Rect(0, 0, 2000, 1000), 1.5f, - new Rect(500, 0, 2000, 1000)); - } - - @Test - public void testPositionLimitedAspectRatioNavBarRight() throws Exception { - verifyPositionWithLimitedAspectRatio(NAV_BAR_RIGHT, new Rect(0, 0, 2000, 1000), 1.5f, - new Rect(0, 0, 1500, 1000)); - } - - private void verifyPositionWithLimitedAspectRatio(int navBarPosition, Rect taskBounds, - float aspectRatio, Rect expectedActivityBounds) { - // Verify with nav bar on the right. - when(mService.mWindowManager.getNavBarPosition()).thenReturn(navBarPosition); - mTask.getConfiguration().windowConfiguration.setAppBounds(taskBounds); - mActivity.info.maxAspectRatio = aspectRatio; - mActivity.ensureActivityConfiguration( - 0 /* globalChanges */, false /* preserveWindow */); - assertEquals(expectedActivityBounds, mActivity.getBounds()); - } - - @Test - public void testCanBeLaunchedOnDisplay() throws Exception { - testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/, - true /*activityResizeable*/, true /*expected*/); - - testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/, - false /*activityResizeable*/, false /*expected*/); - - testSupportsLaunchingResizeable(true /*taskPresent*/, false /*taskResizeable*/, - true /*activityResizeable*/, false /*expected*/); - - testSupportsLaunchingResizeable(true /*taskPresent*/, true /*taskResizeable*/, - false /*activityResizeable*/, true /*expected*/); - } - - @Test - public void testsApplyOptionsLocked() { - ActivityOptions activityOptions = ActivityOptions.makeBasic(); - - // Set and apply options for ActivityRecord. Pending options should be cleared - mActivity.updateOptionsLocked(activityOptions); - mActivity.applyOptionsLocked(); - assertNull(mActivity.pendingOptions); - - // Set options for two ActivityRecords in same Task. Apply one ActivityRecord options. - // Pending options should be cleared for both ActivityRecords - ActivityRecord activity2 = new ActivityBuilder(mService).setTask(mTask).build(); - activity2.updateOptionsLocked(activityOptions); - mActivity.updateOptionsLocked(activityOptions); - mActivity.applyOptionsLocked(); - assertNull(mActivity.pendingOptions); - assertNull(activity2.pendingOptions); - - // Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options. - // Pending options should be cleared for only ActivityRecord that was applied - TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); - activity2 = new ActivityBuilder(mService).setTask(task2).build(); - activity2.updateOptionsLocked(activityOptions); - mActivity.updateOptionsLocked(activityOptions); - mActivity.applyOptionsLocked(); - assertNull(mActivity.pendingOptions); - assertNotNull(activity2.pendingOptions); - } - - private void testSupportsLaunchingResizeable(boolean taskPresent, boolean taskResizeable, - boolean activityResizeable, boolean expected) { - mService.mSupportsMultiWindow = true; - - final TaskRecord task = taskPresent - ? new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build() : null; - - if (task != null) { - task.setResizeMode(taskResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE); - } - - final ActivityRecord record = new ActivityBuilder(mService).setTask(task).build(); - record.info.resizeMode = activityResizeable - ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; - - record.canBeLaunchedOnDisplay(DEFAULT_DISPLAY); - - - verify(mService.mStackSupervisor, times(1)).canPlaceEntityOnDisplay(anyInt(), eq(expected), - anyInt(), anyInt(), eq(record.info)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java deleted file mode 100644 index 1ce41a641935..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.ActivityManager.START_DELIVERED_TO_TOP; -import static android.app.ActivityManager.START_TASK_TO_FRONT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; - -import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; -import static com.android.server.am.ActivityStackSupervisor - .MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.app.ActivityOptions; -import android.app.WaitResult; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.SparseIntArray; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; - -import java.util.ArrayList; - -/** - * Tests for the {@link ActivityStackSupervisor} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.am.ActivityStackSupervisorTests - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ActivityStackSupervisorTests extends ActivityTestsBase { - private ActivityManagerService mService; - private ActivityStackSupervisor mSupervisor; - private ActivityStack mFullscreenStack; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mService = createActivityManagerService(); - mSupervisor = mService.mStackSupervisor; - mFullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - } - - /** - * This test ensures that we do not try to restore a task based off an invalid task id. The - * stack supervisor is a test version so there will be no tasks present. We should expect - * {@code null} to be returned in this case. - */ - @Test - public void testRestoringInvalidTask() throws Exception { - TaskRecord task = mSupervisor.anyTaskForIdLocked(0 /*taskId*/, - MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */); - assertNull(task); - } - - /** - * This test ensures that an existing task in the pinned stack is moved to the fullscreen - * activity stack when a new task is added. - */ - @Test - public void testReplacingTaskInPinnedStack() throws Exception { - final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(mFullscreenStack).build(); - final TaskRecord firstTask = firstActivity.getTask(); - - final ActivityRecord secondActivity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(mFullscreenStack).build(); - final TaskRecord secondTask = secondActivity.getTask(); - - mSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack", mFullscreenStack); - - // Ensure full screen stack has both tasks. - ensureStackPlacement(mFullscreenStack, firstTask, secondTask); - - // Move first activity to pinned stack. - final Rect sourceBounds = new Rect(); - mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds, - 0f /*aspectRatio*/, "initialMove"); - - final ActivityDisplay display = mFullscreenStack.getDisplay(); - ActivityStack pinnedStack = display.getPinnedStack(); - // Ensure a task has moved over. - ensureStackPlacement(pinnedStack, firstTask); - ensureStackPlacement(mFullscreenStack, secondTask); - - // Move second activity to pinned stack. - mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds, - 0f /*aspectRatio*/, "secondMove"); - - // Need to get stacks again as a new instance might have been created. - pinnedStack = display.getPinnedStack(); - mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - // Ensure stacks have swapped tasks. - ensureStackPlacement(pinnedStack, secondTask); - ensureStackPlacement(mFullscreenStack, firstTask); - } - - private static void ensureStackPlacement(ActivityStack stack, TaskRecord... tasks) { - final ArrayList stackTasks = stack.getAllTasks(); - assertEquals(stackTasks.size(), tasks != null ? tasks.length : 0); - - if (tasks == null) { - return; - } - - for (TaskRecord task : tasks) { - assertTrue(stackTasks.contains(task)); - } - } - - /** - * Ensures that an activity is removed from the stopping activities list once it is resumed. - */ - @Test - public void testStoppingActivityRemovedWhenResumed() throws Exception { - final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(mFullscreenStack).build(); - mSupervisor.mStoppingActivities.add(firstActivity); - - firstActivity.completeResumeLocked(); - - assertFalse(mSupervisor.mStoppingActivities.contains(firstActivity)); - } - - /** - * Ensures that waiting results are notified of launches. - */ - @Test - public void testReportWaitingActivityLaunchedIfNeeded() throws Exception { - final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(mFullscreenStack).build(); - - // #notifyAll will be called on the ActivityManagerService. we must hold the object lock - // when this happens. - synchronized (mSupervisor.mService) { - final WaitResult taskToFrontWait = new WaitResult(); - mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait); - mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_TASK_TO_FRONT); - - assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty()); - assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT); - assertEquals(taskToFrontWait.who, null); - - final WaitResult deliverToTopWait = new WaitResult(); - mSupervisor.mWaitingActivityLaunched.add(deliverToTopWait); - mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, - START_DELIVERED_TO_TOP); - - assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty()); - assertEquals(deliverToTopWait.result, START_DELIVERED_TO_TOP); - assertEquals(deliverToTopWait.who, firstActivity.realActivity); - } - } - - @Test - public void testApplySleepTokensLocked() throws Exception { - final ActivityDisplay display = mSupervisor.getDefaultDisplay(); - final KeyguardController keyguard = mSupervisor.getKeyguardController(); - final ActivityStack stack = mock(ActivityStack.class); - display.addChild(stack, 0 /* position */); - - // Make sure we wake and resume in the case the display is turning on and the keyguard is - // not showing. - verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, - false /* displayShouldSleep */, true /* isFocusedStack */, - false /* keyguardShowing */, true /* expectWakeFromSleep */, - true /* expectResumeTopActivity */); - - // Make sure we wake and don't resume when the display is turning on and the keyguard is - // showing. - verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, - false /* displayShouldSleep */, true /* isFocusedStack */, - true /* keyguardShowing */, true /* expectWakeFromSleep */, - false /* expectResumeTopActivity */); - - // Make sure we wake and don't resume when the display is turning on and the keyguard is - // not showing as unfocused. - verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, - false /* displayShouldSleep */, false /* isFocusedStack */, - false /* keyguardShowing */, true /* expectWakeFromSleep */, - false /* expectResumeTopActivity */); - - // Should not do anything if the display state hasn't changed. - verifySleepTokenBehavior(display, keyguard, stack, false /*displaySleeping*/, - false /* displayShouldSleep */, true /* isFocusedStack */, - false /* keyguardShowing */, false /* expectWakeFromSleep */, - false /* expectResumeTopActivity */); - } - - private void verifySleepTokenBehavior(ActivityDisplay display, KeyguardController keyguard, - ActivityStack stack, boolean displaySleeping, boolean displayShouldSleep, - boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep, - boolean expectResumeTopActivity) { - reset(stack); - - doReturn(displayShouldSleep).when(display).shouldSleep(); - doReturn(displaySleeping).when(display).isSleeping(); - doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt()); - - mSupervisor.mFocusedStack = isFocusedStack ? stack : null; - mSupervisor.applySleepTokensLocked(true); - verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); - verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( - null /* target */, null /* targetOptions */); - } - - @Test - public void testTopRunningActivityLockedWithNonExistentDisplay() throws Exception { - // Create display that ActivityManagerService does not know about - final int unknownDisplayId = 100; - - doAnswer((InvocationOnMock invocationOnMock) -> { - final SparseIntArray displayIds = invocationOnMock.getArgument(0); - displayIds.put(0, unknownDisplayId); - return null; - }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); - - mSupervisor.mFocusedStack = mock(ActivityStack.class); - - // Supervisor should skip over the non-existent display. - assertEquals(null, mSupervisor.topRunningActivityLocked()); - } - - /** - * Verifies that removal of activity with task and stack is done correctly. - */ - @Test - public void testRemovingStackOnAppCrash() throws Exception { - final ActivityDisplay defaultDisplay = mService.mStackSupervisor.getDefaultDisplay(); - final int originalStackCount = defaultDisplay.getChildCount(); - final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); - final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(stack).build(); - - assertEquals(originalStackCount + 1, defaultDisplay.getChildCount()); - - // Let's pretend that the app has crashed. - firstActivity.app.thread = null; - mService.mStackSupervisor.finishTopCrashedActivitiesLocked(firstActivity.app, "test"); - - // Verify that the stack was removed. - assertEquals(originalStackCount, defaultDisplay.getChildCount()); - } - - @Test - public void testFocusability() throws Exception { - final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(stack).build(); - - // Under split screen primary we should be focusable when not minimized - mService.mStackSupervisor.setDockedStackMinimized(false); - assertTrue(stack.isFocusable()); - assertTrue(activity.isFocusable()); - - // Under split screen primary we should not be focusable when minimized - mService.mStackSupervisor.setDockedStackMinimized(true); - assertFalse(stack.isFocusable()); - assertFalse(activity.isFocusable()); - - final ActivityStack pinnedStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(pinnedStack).build(); - - // We should not be focusable when in pinned mode - assertFalse(pinnedStack.isFocusable()); - assertFalse(pinnedActivity.isFocusable()); - - // Add flag forcing focusability. - pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE; - - // We should not be focusable when in pinned mode - assertTrue(pinnedStack.isFocusable()); - assertTrue(pinnedActivity.isFocusable()); - - // Without the overridding activity, stack should not be focusable. - pinnedStack.removeTask(pinnedActivity.getTask(), "testFocusability", - REMOVE_TASK_MODE_DESTROYING); - assertFalse(pinnedStack.isFocusable()); - } - - /** - * Verifies the correct activity is returned when querying the top running activity. - */ - @Test - public void testTopRunningActivity() throws Exception { - // Create stack to hold focus - final ActivityStack emptyStack = mService.mStackSupervisor.getDefaultDisplay() - .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - final KeyguardController keyguard = mSupervisor.getKeyguardController(); - final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(stack).build(); - - mSupervisor.mFocusedStack = emptyStack; - - doAnswer((InvocationOnMock invocationOnMock) -> { - final SparseIntArray displayIds = invocationOnMock.getArgument(0); - displayIds.put(0, mSupervisor.getDefaultDisplay().mDisplayId); - return null; - }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); - - // Make sure the top running activity is not affected when keyguard is not locked - assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); - assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked( - true /* considerKeyguardState */)); - - // Check to make sure activity not reported when it cannot show on lock and lock is on. - doReturn(true).when(keyguard).isKeyguardLocked(); - assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); - assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked( - true /* considerKeyguardState */)); - - // Change focus to stack with activity. - mSupervisor.mFocusedStack = stack; - assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); - assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked( - true /* considerKeyguardState */)); - - // Add activity that should be shown on the keyguard. - final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mService) - .setCreateTask(true) - .setStack(stack) - .setActivityFlags(FLAG_SHOW_WHEN_LOCKED) - .build(); - - // Ensure the show when locked activity is returned. - assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked()); - assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked( - true /* considerKeyguardState */)); - - // Change focus back to empty stack - mSupervisor.mFocusedStack = emptyStack; - // Ensure the show when locked activity is returned when not the focused stack - assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked()); - assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked( - true /* considerKeyguardState */)); - } - - /** - * Verify that split-screen primary stack will be chosen if activity is launched that targets - * split-screen secondary, but a matching existing instance is found on top of split-screen - * primary stack. - */ - @Test - public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() throws Exception { - // Create primary split-screen stack with a task and an activity. - final ActivityStack primaryStack = mService.mStackSupervisor.getDefaultDisplay() - .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TaskRecord task = new TaskBuilder(mSupervisor).setStack(primaryStack).build(); - final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build(); - - // Find a launch stack for the top activity in split-screen primary, while requesting - // split-screen secondary. - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); - final ActivityStack result = mSupervisor.getLaunchStack(r, options, task, true /* onTop */); - - // Assert that the primary stack is returned. - assertEquals(primaryStack, result); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java deleted file mode 100644 index 01425ed51b55..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ /dev/null @@ -1,631 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - -import static com.android.server.am.ActivityStack.ActivityState.PAUSING; -import static com.android.server.am.ActivityStack.ActivityState.RESUMED; -import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.pm.ActivityInfo; -import android.os.UserHandle; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.runner.RunWith; -import org.junit.Before; -import org.junit.Test; - -/** - * Tests for the {@link ActivityStack} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.am.ActivityStackTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ActivityStackTests extends ActivityTestsBase { - private ActivityManagerService mService; - private ActivityStackSupervisor mSupervisor; - private ActivityDisplay mDefaultDisplay; - private ActivityStack mStack; - private TaskRecord mTask; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mService = createActivityManagerService(); - mSupervisor = mService.mStackSupervisor; - mDefaultDisplay = mService.mStackSupervisor.getDefaultDisplay(); - mStack = mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); - } - - @Test - public void testEmptyTaskCleanupOnRemove() throws Exception { - assertNotNull(mTask.getWindowContainerController()); - mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); - assertNull(mTask.getWindowContainerController()); - } - - @Test - public void testOccupiedTaskCleanupOnRemove() throws Exception { - final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - assertNotNull(mTask.getWindowContainerController()); - mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); - assertNotNull(mTask.getWindowContainerController()); - } - - @Test - public void testResumedActivity() throws Exception { - final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - assertEquals(mStack.getResumedActivity(), null); - r.setState(RESUMED, "testResumedActivity"); - assertEquals(mStack.getResumedActivity(), r); - r.setState(PAUSING, "testResumedActivity"); - assertEquals(mStack.getResumedActivity(), null); - } - - @Test - public void testResumedActivityFromTaskReparenting() { - final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - // Ensure moving task between two stacks updates resumed activity - r.setState(RESUMED, "testResumedActivityFromTaskReparenting"); - assertEquals(mStack.getResumedActivity(), r); - - final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - mTask.reparent(destStack, true /* toTop */, TaskRecord.REPARENT_KEEP_STACK_AT_FRONT, - false /* animate */, true /* deferResume*/, - "testResumedActivityFromTaskReparenting"); - - assertEquals(mStack.getResumedActivity(), null); - assertEquals(destStack.getResumedActivity(), r); - } - - @Test - public void testResumedActivityFromActivityReparenting() { - final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - // Ensure moving task between two stacks updates resumed activity - r.setState(RESUMED, "testResumedActivityFromActivityReparenting"); - assertEquals(mStack.getResumedActivity(), r); - - final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TaskRecord destTask = new TaskBuilder(mSupervisor).setStack(destStack).build(); - - mTask.removeActivity(r); - destTask.addActivityToTop(r); - - assertEquals(mStack.getResumedActivity(), null); - assertEquals(destStack.getResumedActivity(), r); - } - - @Test - public void testPrimarySplitScreenToFullscreenWhenMovedToBack() throws Exception { - // Create primary splitscreen stack. This will create secondary stacks and places the - // existing fullscreen stack on the bottom. - final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - // Assert windowing mode. - assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - - // Move primary to back. - primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", - null /* task */); - - // Assert that stack is at the bottom. - assertEquals(mDefaultDisplay.getIndexOf(primarySplitScreen), 0); - - // Ensure no longer in splitscreen. - assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_FULLSCREEN); - } - - @Test - public void testStopActivityWhenActivityDestroyed() throws Exception { - final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); - r.info.flags |= ActivityInfo.FLAG_NO_HISTORY; - mSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", mStack); - mStack.stopActivityLocked(r); - // Mostly testing to make sure there is a crash in the call part, so if we get here we are - // good-to-go! - } - - @Test - public void testFindTaskWithOverlay() throws Exception { - final ActivityRecord r = new ActivityBuilder(mService) - .setCreateTask(true) - .setStack(mStack) - .setUid(0) - .build(); - final TaskRecord task = r.getTask(); - // Overlay must be for a different user to prevent recognizing a matching top activity - final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task) - .setUid(UserHandle.PER_USER_RANGE * 2).build(); - taskOverlay.mTaskOverlay = true; - - final ActivityStackSupervisor.FindTaskResult result = - new ActivityStackSupervisor.FindTaskResult(); - mStack.findTaskLocked(r, result); - - assertEquals(task.getTopActivity(false /* includeOverlays */), r); - assertEquals(task.getTopActivity(true /* includeOverlays */), taskOverlay); - assertNotNull(result.r); - } - - @Test - public void testShouldBeVisible_Fullscreen() throws Exception { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack - // should be visible since it is always on-top. - fullscreenStack.setIsTranslucent(false); - assertFalse(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); - - // Home stack should be visible behind a translucent fullscreen stack. - fullscreenStack.setIsTranslucent(true); - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); - } - - @Test - public void testShouldBeVisible_SplitScreen() throws Exception { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - // Home stack should always be fullscreen for this test. - homeStack.setSupportsSplitScreen(false); - final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - // Home stack shouldn't be visible if both halves of split-screen are opaque. - splitScreenPrimary.setIsTranslucent(false); - splitScreenSecondary.setIsTranslucent(false); - assertFalse(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - - // Home stack should be visible if one of the halves of split-screen is translucent. - splitScreenPrimary.setIsTranslucent(true); - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - - final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - // First split-screen secondary shouldn't be visible behind another opaque split-split - // secondary. - splitScreenSecondary2.setIsTranslucent(false); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - - // First split-screen secondary should be visible behind another translucent split-screen - // secondary. - splitScreenSecondary2.setIsTranslucent(true); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - - final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - - // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. - assistantStack.setIsTranslucent(false); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - - // Split-screen stacks should be visible behind a translucent fullscreen stack. - assistantStack.setIsTranslucent(true); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - - // Assistant stack shouldn't be visible behind translucent split-screen stack - assistantStack.setIsTranslucent(false); - splitScreenPrimary.setIsTranslucent(true); - splitScreenSecondary2.setIsTranslucent(true); - splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen"); - splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen"); - assertFalse(assistantStack.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); - } - - @Test - public void testShouldBeVisible_Finishing() throws Exception { - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack translucentStack = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - translucentStack.setIsTranslucent(true); - - assertTrue(homeStack.shouldBeVisible(null /* starting */)); - assertTrue(translucentStack.shouldBeVisible(null /* starting */)); - - final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); - topRunningHomeActivity.finishing = true; - final ActivityRecord topRunningTranslucentActivity = - translucentStack.topRunningActivityLocked(); - topRunningTranslucentActivity.finishing = true; - - // Home shouldn't be visible since its activity is marked as finishing and it isn't the top - // of the stack list. - assertFalse(homeStack.shouldBeVisible(null /* starting */)); - // Home should be visible if we are starting an activity within it. - assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */)); - // The translucent stack should be visible since it is the top of the stack list even though - // it has its activity marked as finishing. - assertTrue(translucentStack.shouldBeVisible(null /* starting */)); - } - - @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - homeStack.setIsTranslucent(false); - fullscreenStack.setIsTranslucent(false); - - // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack); - mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); - assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); - } - - @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - homeStack.setIsTranslucent(false); - fullscreenStack.setIsTranslucent(true); - - // Ensure that we don't move the home stack if it is already behind the top fullscreen stack - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack); - mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); - assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); - } - - @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - - homeStack.setIsTranslucent(false); - fullscreenStack.setIsTranslucent(false); - - // Ensure we don't move the home stack if it is already on top - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == null); - mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); - assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); - } - - @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - homeStack.setIsTranslucent(false); - fullscreenStack1.setIsTranslucent(false); - fullscreenStack2.setIsTranslucent(false); - - // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the - // pinned stack - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); - mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); - } - - @Test - public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - - homeStack.setIsTranslucent(false); - fullscreenStack1.setIsTranslucent(false); - fullscreenStack2.setIsTranslucent(true); - - // Ensure that we move the home stack behind the bottom most non-translucent fullscreen - // stack - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); - mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); - } - - @Test - public void testMoveHomeStackBehindStack_BehindHomeStack() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - - homeStack.setIsTranslucent(false); - fullscreenStack1.setIsTranslucent(false); - fullscreenStack2.setIsTranslucent(false); - - // Ensure we don't move the home stack behind itself - int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); - mDefaultDisplay.moveStackBehindStack(homeStack, homeStack); - assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); - } - - @Test - public void testMoveHomeStackBehindStack() { - mDefaultDisplay.removeChild(mStack); - - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - - mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack1); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); - mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); - mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack4); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack4); - mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2); - assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); - } - - @Test - public void testSplitScreenMoveToFront() throws Exception { - final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - - splitScreenPrimary.setIsTranslucent(false); - splitScreenSecondary.setIsTranslucent(false); - assistantStack.setIsTranslucent(false); - - assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantStack.shouldBeVisible(null /* starting */)); - - splitScreenSecondary.moveToFront("testSplitScreenMoveToFront"); - - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertFalse(assistantStack.shouldBeVisible(null /* starting */)); - } - - private T createStackForShouldBeVisibleTest( - ActivityDisplay display, int windowingMode, int activityType, boolean onTop) { - final T stack = display.createStack(windowingMode, activityType, onTop); - final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack) - .setCreateTask(true).build(); - return stack; - } - - @Test - public void testFinishDisabledPackageActivities() throws Exception { - final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build(); - final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build(); - - // Making the second activity a task overlay without an app means it will be removed from - // the task's activities as well once first activity is removed. - secondActivity.mTaskOverlay = true; - secondActivity.app = null; - - assertEquals(mTask.mActivities.size(), 2); - - mStack.finishDisabledPackageActivitiesLocked(firstActivity.packageName, null, - true /* doit */, true /* evenPersistent */, UserHandle.USER_ALL); - - assertTrue(mTask.mActivities.isEmpty()); - assertTrue(mStack.getAllTasks().isEmpty()); - } - - @Test - public void testHandleAppDied() throws Exception { - final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build(); - final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build(); - - // Making the first activity a task overlay means it will be removed from the task's - // activities as well once second activity is removed as handleAppDied processes the - // activity list in reverse. - firstActivity.mTaskOverlay = true; - firstActivity.app = null; - - // second activity will be immediately removed as it has no state. - secondActivity.haveState = false; - - assertEquals(mTask.mActivities.size(), 2); - - mStack.handleAppDiedLocked(secondActivity.app); - - assertTrue(mTask.mActivities.isEmpty()); - assertTrue(mStack.getAllTasks().isEmpty()); - } - - @Test - public void testShouldSleepActivities() throws Exception { - // When focused activity and keyguard is going away, we should not sleep regardless - // of the display state - verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/, - true /* displaySleeping */, false /* expected*/); - - // When not the focused stack, defer to display sleeping state. - verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/, - true /* displaySleeping */, true /* expected*/); - - // If keyguard is going away, defer to the display sleeping state. - verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/, - true /* displaySleeping */, true /* expected*/); - verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/, - false /* displaySleeping */, false /* expected*/); - } - - @Test - public void testStackOrderChangedOnRemoveStack() throws Exception { - StackOrderChangedListener listener = new StackOrderChangedListener(); - mDefaultDisplay.registerStackOrderChangedListener(listener); - try { - mDefaultDisplay.removeChild(mStack); - } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); - } - assertTrue(listener.changed); - } - - @Test - public void testStackOrderChangedOnAddPositionStack() throws Exception { - mDefaultDisplay.removeChild(mStack); - - StackOrderChangedListener listener = new StackOrderChangedListener(); - mDefaultDisplay.registerStackOrderChangedListener(listener); - try { - mDefaultDisplay.addChild(mStack, 0); - } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); - } - assertTrue(listener.changed); - } - - @Test - public void testStackOrderChangedOnPositionStack() throws Exception { - StackOrderChangedListener listener = new StackOrderChangedListener(); - try { - final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( - mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, - true /* onTop */); - mDefaultDisplay.registerStackOrderChangedListener(listener); - mDefaultDisplay.positionChildAtBottom(fullscreenStack1); - } finally { - mDefaultDisplay.unregisterStackOrderChangedListener(listener); - } - assertTrue(listener.changed); - } - - private void verifyShouldSleepActivities(boolean focusedStack, - boolean keyguardGoingAway, boolean displaySleeping, boolean expected) { - mSupervisor.mFocusedStack = focusedStack ? mStack : null; - - final ActivityDisplay display = mock(ActivityDisplay.class); - final KeyguardController keyguardController = mSupervisor.getKeyguardController(); - - doReturn(display).when(mSupervisor).getActivityDisplay(anyInt()); - doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway(); - doReturn(displaySleeping).when(display).isSleeping(); - - assertEquals(expected, mStack.shouldSleepActivities()); - } - - private class StackOrderChangedListener implements ActivityDisplay.OnStackOrderChangedListener { - boolean changed = false; - - @Override - public void onStackOrderChanged() { - changed = true; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java deleted file mode 100644 index 7948e4cfcaaa..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStartControllerTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; - -import android.content.Intent; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; -import com.android.server.am.ActivityStarter.Factory; - -import org.junit.runner.RunWith; -import org.junit.Test; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.times; - -import java.util.Random; - -/** - * Tests for the {@link ActivityStartController} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:ActivityStartControllerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ActivityStartControllerTests extends ActivityTestsBase { - private ActivityManagerService mService; - private ActivityStartController mController; - private Factory mFactory; - private ActivityStarter mStarter; - - @Override - public void setUp() throws Exception { - super.setUp(); - mService = createActivityManagerService(); - mFactory = mock(Factory.class); - mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory); - mStarter = spy(new ActivityStarter(mController, mService, mService.mStackSupervisor, - mock(ActivityStartInterceptor.class))); - doReturn(mStarter).when(mFactory).obtain(); - } - - /** - * Ensures that pending launches are processed. - */ - @Test - public void testPendingActivityLaunches() { - final Random random = new Random(); - - final ActivityRecord activity = new ActivityBuilder(mService).build(); - final ActivityRecord source = new ActivityBuilder(mService).build(); - final int startFlags = random.nextInt(); - final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ProcessRecord process= new ProcessRecord(null, null, - mService.mContext.getApplicationInfo(), "name", 12345); - - mController.addPendingActivityLaunch( - new PendingActivityLaunch(activity, source, startFlags, stack, process)); - final boolean resume = random.nextBoolean(); - mController.doPendingActivityLaunches(resume); - - verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null), - eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null)); - } - - - /** - * Ensures instances are recycled after execution. - */ - @Test - public void testRecycling() throws Exception { - final Intent intent = new Intent(); - final ActivityStarter optionStarter = new ActivityStarter(mController, mService, - mService.mStackSupervisor, mock(ActivityStartInterceptor.class)); - optionStarter - .setIntent(intent) - .setReason("Test") - .execute(); - verify(mFactory, times(1)).recycle(eq(optionStarter)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java deleted file mode 100644 index b4b34c507942..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; - -import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.when; - -import android.app.KeyguardManager; -import android.app.admin.DevicePolicyManagerInternal; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManagerInternal; -import android.content.pm.UserInfo; -import android.os.UserHandle; -import android.os.UserManager; -import android.support.test.filters.SmallTest; -import android.testing.DexmakerShareClassLoaderRule; - -import com.android.internal.app.SuspendedAppActivity; -import com.android.internal.app.UnlaunchableAppActivity; -import com.android.server.LocalServices; -import com.android.server.pm.PackageManagerService; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Unit tests for {@link ActivityStartInterceptorTest}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.am.ActivityStartInterceptorTest - */ -@SmallTest -public class ActivityStartInterceptorTest { - private static final int TEST_USER_ID = 1; - private static final int TEST_REAL_CALLING_UID = 2; - private static final int TEST_REAL_CALLING_PID = 3; - private static final String TEST_CALLING_PACKAGE = "com.test.caller"; - private static final int TEST_START_FLAGS = 4; - private static final Intent ADMIN_SUPPORT_INTENT = - new Intent("com.test.ADMIN_SUPPORT"); - private static final Intent CONFIRM_CREDENTIALS_INTENT = - new Intent("com.test.CONFIRM_CREDENTIALS"); - private static final UserInfo PARENT_USER_INFO = new UserInfo(0 /* userId */, "parent", - 0 /* flags */); - private static final String TEST_PACKAGE_NAME = "com.test.package"; - - @Rule - public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = - new DexmakerShareClassLoaderRule(); - - @Mock - private Context mContext; - @Mock - private ActivityManagerService mService; - @Mock - private ActivityStackSupervisor mSupervisor; - @Mock - private DevicePolicyManagerInternal mDevicePolicyManager; - @Mock - private PackageManagerInternal mPackageManagerInternal; - @Mock - private UserManager mUserManager; - @Mock - private UserController mUserController; - @Mock - private KeyguardManager mKeyguardManager; - @Mock - private PackageManagerService mPackageManager; - - private ActivityStartInterceptor mInterceptor; - private ActivityInfo mAInfo = new ActivityInfo(); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext, - mUserController); - mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID, - TEST_START_FLAGS, TEST_CALLING_PACKAGE); - - // Mock DevicePolicyManagerInternal - LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); - LocalServices.addService(DevicePolicyManagerInternal.class, - mDevicePolicyManager); - when(mDevicePolicyManager - .createShowAdminSupportIntent(TEST_USER_ID, true)) - .thenReturn(ADMIN_SUPPORT_INTENT); - when(mService.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal); - - // Mock UserManager - when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); - when(mUserManager.getProfileParent(TEST_USER_ID)).thenReturn(PARENT_USER_INFO); - - // Mock KeyguardManager - when(mContext.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(mKeyguardManager); - when(mKeyguardManager.createConfirmDeviceCredentialIntent( - nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID))). - thenReturn(CONFIRM_CREDENTIALS_INTENT); - - // Mock PackageManager - when(mService.getPackageManager()).thenReturn(mPackageManager); - when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID)) - .thenReturn(null); - - // Initialise activity info - mAInfo.applicationInfo = new ApplicationInfo(); - mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; - } - - @Test - public void testSuspendedByAdminPackage() { - // GIVEN the package we're about to launch is currently suspended - mAInfo.applicationInfo.flags = FLAG_SUSPENDED; - - when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID)) - .thenReturn(PLATFORM_PACKAGE_NAME); - - // THEN calling intercept returns true - assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); - - // THEN the returned intent is the admin support intent - assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent); - } - - @Test - public void testSuspendedPackage() { - mAInfo.applicationInfo.flags = FLAG_SUSPENDED; - final String suspendingPackage = "com.test.suspending.package"; - final String dialogMessage = "Test Message"; - when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID)) - .thenReturn(suspendingPackage); - when(mPackageManagerInternal.getSuspendedDialogMessage(TEST_PACKAGE_NAME, TEST_USER_ID)) - .thenReturn(dialogMessage); - // THEN calling intercept returns true - assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); - - // Check intent parameters - assertEquals(dialogMessage, - mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_DIALOG_MESSAGE)); - assertEquals(suspendingPackage, - mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDING_PACKAGE)); - assertEquals(TEST_PACKAGE_NAME, - mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDED_PACKAGE)); - assertEquals(TEST_USER_ID, mInterceptor.mIntent.getIntExtra(Intent.EXTRA_USER_ID, -1000)); - } - - @Test - public void testInterceptQuietProfile() { - // GIVEN that the user the activity is starting as is currently in quiet mode - when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); - - // THEN calling intercept returns true - assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); - - // THEN the returned intent is the quiet mode intent - assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID) - .filterEquals(mInterceptor.mIntent)); - } - - @Test - public void testWorkChallenge() { - // GIVEN that the user the activity is starting as is currently locked - when(mUserController.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true); - - // THEN calling intercept returns true - mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null); - - // THEN the returned intent is the quiet mode intent - assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent)); - } - - @Test - public void testNoInterception() { - // GIVEN that none of the interception conditions are met - - // THEN calling intercept returns false - assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java deleted file mode 100644 index 1520859d4aac..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.ActivityManager.START_ABORTED; -import static android.app.ActivityManager.START_CLASS_NOT_FOUND; -import static android.app.ActivityManager.START_DELIVERED_TO_TOP; -import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; -import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED; -import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE; -import static android.app.ActivityManager.START_PERMISSION_DENIED; -import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; -import static android.app.ActivityManager.START_SUCCESS; -import static android.app.ActivityManager.START_SWITCHES_CANCELED; -import static android.app.ActivityManager.START_TASK_TO_FRONT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - -import android.app.ActivityOptions; -import android.app.IApplicationThread; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ActivityInfo.WindowLayout; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.graphics.Rect; -import android.os.IBinder; -import android.os.RemoteException; -import android.platform.test.annotations.Presubmit; -import android.service.voice.IVoiceInteractionSession; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.Gravity; - -import org.junit.runner.RunWith; -import org.junit.Test; - -import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; -import static com.android.server.am.ActivityManagerService.ANIMATE; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyObject; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.times; - -import com.android.internal.os.BatteryStatsImpl; -import com.android.server.am.ActivityStarter.Factory; -import com.android.server.am.LaunchParamsController.LaunchParamsModifier; -import com.android.server.am.TaskRecord.TaskRecordFactory; - -import java.util.ArrayList; - -/** - * Tests for the {@link ActivityStarter} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:ActivityStarterTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ActivityStarterTests extends ActivityTestsBase { - private ActivityManagerService mService; - private ActivityStarter mStarter; - private ActivityStartController mController; - - private static final int PRECONDITION_NO_CALLER_APP = 1; - private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1; - private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2; - private static final int PRECONDITION_SOURCE_PRESENT = 1 << 3; - private static final int PRECONDITION_REQUEST_CODE = 1 << 4; - private static final int PRECONDITION_SOURCE_VOICE_SESSION = 1 << 5; - private static final int PRECONDITION_NO_VOICE_SESSION_SUPPORT = 1 << 6; - private static final int PRECONDITION_DIFFERENT_UID = 1 << 7; - private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8; - private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9; - private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10; - - @Override - public void setUp() throws Exception { - super.setUp(); - mService = createActivityManagerService(); - mController = mock(ActivityStartController.class); - mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor, - mock(ActivityStartInterceptor.class)); - } - - @Test - public void testUpdateLaunchBounds() throws Exception { - // When in a non-resizeable stack, the task bounds should be updated. - final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) - .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) - .build(); - final Rect bounds = new Rect(10, 10, 100, 100); - - mStarter.updateBounds(task, bounds); - assertEquals(task.getOverrideBounds(), bounds); - assertEquals(new Rect(), task.getStack().getOverrideBounds()); - - // When in a resizeable stack, the stack bounds should be updated as well. - final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor) - .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */)) - .build(); - assertTrue(task2.getStack() instanceof PinnedActivityStack); - mStarter.updateBounds(task2, bounds); - - verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId), - eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt()); - - // In the case of no animation, the stack and task bounds should be set immediately. - if (!ANIMATE) { - assertEquals(task2.getStack().getOverrideBounds(), bounds); - assertEquals(task2.getOverrideBounds(), bounds); - } else { - assertEquals(task2.getOverrideBounds(), new Rect()); - } - } - - @Test - public void testStartActivityPreconditions() throws Exception { - verifyStartActivityPreconditions(PRECONDITION_NO_CALLER_APP, START_PERMISSION_DENIED); - verifyStartActivityPreconditions(PRECONDITION_NO_INTENT_COMPONENT, - START_INTENT_NOT_RESOLVED); - verifyStartActivityPreconditions(PRECONDITION_NO_ACTIVITY_INFO, START_CLASS_NOT_FOUND); - verifyStartActivityPreconditions(PRECONDITION_SOURCE_PRESENT | PRECONDITION_REQUEST_CODE, - Intent.FLAG_ACTIVITY_FORWARD_RESULT, START_FORWARD_AND_REQUEST_CONFLICT); - verifyStartActivityPreconditions( - PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT - | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID, - START_NOT_VOICE_COMPATIBLE); - verifyStartActivityPreconditions( - PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT - | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID - | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION, - START_NOT_VOICE_COMPATIBLE); - verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED); - verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING, - START_SWITCHES_CANCELED); - } - - private static boolean containsConditions(int preconditions, int mask) { - return (preconditions & mask) == mask; - } - - private void verifyStartActivityPreconditions(int preconditions, int expectedResult) { - verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult); - } - - /** - * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller - * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP}) - * and the launch flags specified in the intent. The method constructs a call to - * {@link ActivityStarter#execute} based on these preconditions and ensures the result matches - * the expected. It is important to note that the method also checks side effects of the start, - * such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios. - * @param preconditions A bitmask representing the preconditions for the launch - * @param launchFlags The launch flags to be provided by the launch {@link Intent}. - * @param expectedResult The expected result from the launch. - */ - private void verifyStartActivityPreconditions(int preconditions, int launchFlags, - int expectedResult) { - final ActivityManagerService service = createActivityManagerService(); - final IPackageManager packageManager = mock(IPackageManager.class); - final ActivityStartController controller = mock(ActivityStartController.class); - - final ActivityStarter starter = new ActivityStarter(controller, service, - service.mStackSupervisor, mock(ActivityStartInterceptor.class)); - final IApplicationThread caller = mock(IApplicationThread.class); - - // If no caller app, return {@code null} {@link ProcessRecord}. - final ProcessRecord record = containsConditions(preconditions, PRECONDITION_NO_CALLER_APP) - ? null : new ProcessRecord(null, mock(BatteryStatsImpl.class), - mock(ApplicationInfo.class), null, 0); - - doReturn(record).when(service).getRecordForAppLocked(anyObject()); - - final Intent intent = new Intent(); - intent.setFlags(launchFlags); - - final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO) - ? null : new ActivityInfo(); - - IVoiceInteractionSession voiceSession = - containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION) - ? mock(IVoiceInteractionSession.class) : null; - - // Create source token - final ActivityBuilder builder = new ActivityBuilder(service).setTask( - new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build()); - - if (aInfo != null) { - aInfo.applicationInfo = new ApplicationInfo(); - aInfo.applicationInfo.packageName = - ActivityBuilder.getDefaultComponent().getPackageName(); - } - - // Offset uid by one from {@link ActivityInfo} to simulate different uids. - if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) { - builder.setUid(aInfo.applicationInfo.uid + 1); - } - - final ActivityRecord source = builder.build(); - - if (!containsConditions(preconditions, PRECONDITION_NO_INTENT_COMPONENT)) { - intent.setComponent(source.realActivity); - } - - if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) { - doReturn(false).when(service).checkAppSwitchAllowedLocked(anyInt(), anyInt(), anyInt(), - anyInt(), any()); - } - - if (containsConditions(preconditions,PRECONDITION_CANNOT_START_ANY_ACTIVITY)) { - doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission( - any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), - anyBoolean(), anyBoolean(), any(), any(), any()); - } - - try { - if (containsConditions(preconditions, - PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION)) { - doAnswer((inv) -> { - throw new RemoteException(); - }).when(packageManager).activitySupportsIntent(eq(source.realActivity), eq(intent), - any()); - } else { - doReturn(!containsConditions(preconditions, PRECONDITION_NO_VOICE_SESSION_SUPPORT)) - .when(packageManager).activitySupportsIntent(eq(source.realActivity), - eq(intent), any()); - } - } catch (RemoteException e) { - } - - final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT) - || containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION) - ? source.appToken : null; - - final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE) - ? 1 : 0; - - final int result = starter.setCaller(caller) - .setIntent(intent) - .setActivityInfo(aInfo) - .setResultTo(resultTo) - .setRequestCode(requestCode) - .setReason("testLaunchActivityPermissionDenied") - .execute(); - - // In some cases the expected result internally is different than the published result. We - // must use ActivityStarter#getExternalResult to translate. - assertEquals(ActivityStarter.getExternalResult(expectedResult), result); - - // Ensure that {@link ActivityOptions} are aborted with unsuccessful result. - if (expectedResult != START_SUCCESS) { - final ActivityStarter optionStarter = new ActivityStarter(mController, mService, - mService.mStackSupervisor, mock(ActivityStartInterceptor.class)); - final ActivityOptions options = spy(ActivityOptions.makeBasic()); - - final int optionResult = optionStarter.setCaller(caller) - .setIntent(intent) - .setActivityInfo(aInfo) - .setResultTo(resultTo) - .setRequestCode(requestCode) - .setReason("testLaunchActivityPermissionDenied") - .setActivityOptions(new SafeActivityOptions(options)) - .execute(); - verify(options, times(1)).abort(); - } - } - - private ActivityStarter prepareStarter(int launchFlags) { - // always allow test to start activity. - doReturn(true).when(mService.mStackSupervisor).checkStartAnyActivityPermission( - any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), - anyBoolean(), anyBoolean(), any(), any(), any()); - - // instrument the stack and task used. - final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) - .setCreateStack(false) - .build(); - - // supervisor needs a focused stack. - mService.mStackSupervisor.mFocusedStack = stack; - - // use factory that only returns spy task. - final TaskRecordFactory factory = mock(TaskRecordFactory.class); - TaskRecord.setTaskRecordFactory(factory); - - // return task when created. - doReturn(task).when(factory).create(any(), anyInt(), any(), any(), any(), any()); - - // direct starter to use spy stack. - doReturn(stack).when(mService.mStackSupervisor) - .getLaunchStack(any(), any(), any(), anyBoolean()); - doReturn(stack).when(mService.mStackSupervisor) - .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt()); - - final Intent intent = new Intent(); - intent.addFlags(launchFlags); - intent.setComponent(ActivityBuilder.getDefaultComponent()); - - final ActivityInfo info = new ActivityInfo(); - - info.applicationInfo = new ApplicationInfo(); - info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName(); - - return new ActivityStarter(mController, mService, - mService.mStackSupervisor, mock(ActivityStartInterceptor.class)) - .setIntent(intent) - .setActivityInfo(info); - } - - /** - * Ensures that values specified at launch time are passed to {@link LaunchParamsModifier} - * when we are laying out a new task. - */ - @Test - public void testCreateTaskLayout() { - // modifier for validating passed values. - final LaunchParamsModifier modifier = mock(LaunchParamsModifier.class); - mService.mStackSupervisor.getLaunchParamsController().registerModifier(modifier); - - // add custom values to activity info to make unique. - final ActivityInfo info = new ActivityInfo(); - final Rect launchBounds = new Rect(0, 0, 20, 30); - - final WindowLayout windowLayout = - new WindowLayout(10, .5f, 20, 1.0f, Gravity.NO_GRAVITY, 1, 1); - - info.windowLayout = windowLayout; - info.applicationInfo = new ApplicationInfo(); - info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName(); - - // create starter. - final ActivityStarter optionStarter = prepareStarter(0 /* launchFlags */); - - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchBounds(launchBounds); - - // run starter. - optionStarter - .setReason("testCreateTaskLayout") - .setActivityInfo(info) - .setActivityOptions(new SafeActivityOptions(options)) - .execute(); - - // verify that values are passed to the modifier. - verify(modifier, times(1)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options), - any(), any()); - } - - /** - * This test ensures that if the intent is being delivered to a - */ - @Test - public void testSplitScreenDeliverToTop() { - final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - - final ActivityRecord focusActivity = new ActivityBuilder(mService) - .setCreateTask(true) - .build(); - - focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - - final ActivityRecord reusableActivity = new ActivityBuilder(mService) - .setCreateTask(true) - .build(); - - // Create reusable activity after entering split-screen so that it is the top secondary - // stack. - reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); - - // Set focus back to primary. - mService.mStackSupervisor.setFocusStackUnchecked("testSplitScreenDeliverToTop", - focusActivity.getStack()); - - doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt()); - - final int result = starter.setReason("testSplitScreenDeliverToTop").execute(); - - // Ensure result is delivering intent to top. - assertEquals(result, START_DELIVERED_TO_TOP); - } - - /** - * This test ensures that if the intent is being delivered to a split-screen unfocused task - * reports it is brought to front instead of delivering to top. - */ - @Test - public void testSplitScreenTaskToFront() { - final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - - // Create reusable activity here first. Setting the windowing mode of the primary stack - // will move the existing standard full screen stack to secondary, putting this one on the - // bottom. - final ActivityRecord reusableActivity = new ActivityBuilder(mService) - .setCreateTask(true) - .build(); - - reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); - - final ActivityRecord focusActivity = new ActivityBuilder(mService) - .setCreateTask(true) - .build(); - - // Enter split-screen. Primary stack should have focus. - focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - - doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt()); - - final int result = starter.setReason("testSplitScreenMoveToFront").execute(); - - // Ensure result is moving task to front. - assertEquals(result, START_TASK_TO_FRONT); - } - - /** - * Tests activity is cleaned up properly in a task mode violation. - */ - @Test - public void testTaskModeViolation() { - final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); - assertNoTasks(display); - - final ActivityStarter starter = prepareStarter(0); - - final LockTaskController lockTaskController = mService.getLockTaskController(); - doReturn(true).when(lockTaskController).isLockTaskModeViolation(any()); - - final int result = starter.setReason("testTaskModeViolation").execute(); - - assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result); - assertNoTasks(display); - } - - private void assertNoTasks(ActivityDisplay display) { - for (int i = display.getChildCount() - 1; i >= 0; --i) { - final ActivityStack stack = display.getChildAt(i); - assertTrue(stack.getAllTasks().isEmpty()); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java deleted file mode 100644 index 1cd111fce0ec..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ /dev/null @@ -1,593 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.view.Display.DEFAULT_DISPLAY; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.spy; - -import android.app.ActivityOptions; -import com.android.server.wm.DisplayWindowController; - -import org.junit.Rule; -import org.mockito.invocation.InvocationOnMock; - -import android.app.IApplicationThread; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.os.HandlerThread; -import android.os.Looper; -import android.service.voice.IVoiceInteractionSession; -import android.support.test.InstrumentationRegistry; -import android.testing.DexmakerShareClassLoaderRule; - - -import com.android.internal.app.IVoiceInteractor; - -import com.android.server.AttributeCache; -import com.android.server.wm.AppWindowContainerController; -import com.android.server.wm.PinnedStackWindowController; -import com.android.server.wm.StackWindowController; -import com.android.server.wm.TaskWindowContainerController; -import com.android.server.wm.WindowManagerService; -import com.android.server.wm.WindowTestUtils; -import org.junit.After; -import org.junit.Before; -import org.mockito.MockitoAnnotations; - - -/** - * A base class to handle common operations in activity related unit tests. - */ -public class ActivityTestsBase { - private static boolean sOneTimeSetupDone = false; - - @Rule - public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = - new DexmakerShareClassLoaderRule(); - - private final Context mContext = InstrumentationRegistry.getContext(); - private HandlerThread mHandlerThread; - - // Default package name - static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo"; - - // Default base activity name - private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity"; - - @Before - public void setUp() throws Exception { - if (!sOneTimeSetupDone) { - sOneTimeSetupDone = true; - MockitoAnnotations.initMocks(this); - } - mHandlerThread = new HandlerThread("ActivityTestsBaseThread"); - mHandlerThread.start(); - } - - @After - public void tearDown() { - mHandlerThread.quitSafely(); - } - - protected ActivityManagerService createActivityManagerService() { - final ActivityManagerService service = - setupActivityManagerService(new TestActivityManagerService(mContext)); - AttributeCache.init(mContext); - return service; - } - - protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) { - service = spy(service); - doReturn(mock(IPackageManager.class)).when(service).getPackageManager(); - doNothing().when(service).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt()); - service.mWindowManager = prepareMockWindowManager(); - return service; - } - - /** - * Builder for creating new activities. - */ - protected static class ActivityBuilder { - // An id appended to the end of the component name to make it unique - private static int sCurrentActivityId = 0; - - - - private final ActivityManagerService mService; - - private ComponentName mComponent; - private TaskRecord mTaskRecord; - private int mUid; - private boolean mCreateTask; - private ActivityStack mStack; - private int mActivityFlags; - - ActivityBuilder(ActivityManagerService service) { - mService = service; - } - - ActivityBuilder setComponent(ComponentName component) { - mComponent = component; - return this; - } - - static ComponentName getDefaultComponent() { - return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, - DEFAULT_COMPONENT_PACKAGE_NAME); - } - - ActivityBuilder setTask(TaskRecord task) { - mTaskRecord = task; - return this; - } - - ActivityBuilder setActivityFlags(int flags) { - mActivityFlags = flags; - return this; - } - - ActivityBuilder setStack(ActivityStack stack) { - mStack = stack; - return this; - } - - ActivityBuilder setCreateTask(boolean createTask) { - mCreateTask = createTask; - return this; - } - - ActivityBuilder setUid(int uid) { - mUid = uid; - return this; - } - - ActivityRecord build() { - if (mComponent == null) { - final int id = sCurrentActivityId++; - mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, - DEFAULT_COMPONENT_CLASS_NAME + id); - } - - if (mCreateTask) { - mTaskRecord = new TaskBuilder(mService.mStackSupervisor) - .setComponent(mComponent) - .setStack(mStack).build(); - } - - Intent intent = new Intent(); - intent.setComponent(mComponent); - final ActivityInfo aInfo = new ActivityInfo(); - aInfo.applicationInfo = new ApplicationInfo(); - aInfo.applicationInfo.packageName = mComponent.getPackageName(); - aInfo.applicationInfo.uid = mUid; - aInfo.flags |= mActivityFlags; - - final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, - 0 /* launchedFromPid */, 0, null, intent, null, - aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */, - 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */, - mService.mStackSupervisor, null /* options */, null /* sourceRecord */); - activity.mWindowContainerController = mock(AppWindowContainerController.class); - - if (mTaskRecord != null) { - mTaskRecord.addActivityToTop(activity); - } - - activity.setProcess(new ProcessRecord(null, null, - mService.mContext.getApplicationInfo(), "name", 12345)); - activity.app.thread = mock(IApplicationThread.class); - - return activity; - } - } - - /** - * Builder for creating new tasks. - */ - protected static class TaskBuilder { - // Default package name - static final String DEFAULT_PACKAGE = "com.bar"; - - private final ActivityStackSupervisor mSupervisor; - - private ComponentName mComponent; - private String mPackage; - private int mFlags = 0; - private int mTaskId = 0; - private int mUserId = 0; - private IVoiceInteractionSession mVoiceSession; - private boolean mCreateStack = true; - - private ActivityStack mStack; - - TaskBuilder(ActivityStackSupervisor supervisor) { - mSupervisor = supervisor; - } - - TaskBuilder setComponent(ComponentName component) { - mComponent = component; - return this; - } - - TaskBuilder setPackage(String packageName) { - mPackage = packageName; - return this; - } - - /** - * Set to {@code true} by default, set to {@code false} to prevent the task from - * automatically creating a parent stack. - */ - TaskBuilder setCreateStack(boolean createStack) { - mCreateStack = createStack; - return this; - } - - TaskBuilder setVoiceSession(IVoiceInteractionSession session) { - mVoiceSession = session; - return this; - } - - TaskBuilder setFlags(int flags) { - mFlags = flags; - return this; - } - - TaskBuilder setTaskId(int taskId) { - mTaskId = taskId; - return this; - } - - TaskBuilder setUserId(int userId) { - mUserId = userId; - return this; - } - - TaskBuilder setStack(ActivityStack stack) { - mStack = stack; - return this; - } - - TaskRecord build() { - if (mStack == null && mCreateStack) { - mStack = mSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - } - - final ActivityInfo aInfo = new ActivityInfo(); - aInfo.applicationInfo = new ApplicationInfo(); - aInfo.applicationInfo.packageName = mPackage; - - Intent intent = new Intent(); - if (mComponent == null) { - mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, - DEFAULT_COMPONENT_CLASS_NAME); - } - - intent.setComponent(mComponent); - intent.setFlags(mFlags); - - final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo, - intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/); - task.userId = mUserId; - - if (mStack != null) { - mSupervisor.setFocusStackUnchecked("test", mStack); - mStack.addTask(task, true, "creating test task"); - task.setStack(mStack); - task.setWindowContainerController(); - } - - task.touchActiveTime(); - - return task; - } - - private static class TestTaskRecord extends TaskRecord { - TestTaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, - Intent _intent, IVoiceInteractionSession _voiceSession, - IVoiceInteractor _voiceInteractor) { - super(service, _taskId, info, _intent, _voiceSession, _voiceInteractor); - } - - @Override - void createWindowContainer(boolean onTop, boolean showForAllUsers) { - setWindowContainerController(); - } - - private void setWindowContainerController() { - setWindowContainerController(mock(TaskWindowContainerController.class)); - } - } - } - - /** - * An {@link ActivityManagerService} subclass which provides a test - * {@link ActivityStackSupervisor}. - */ - protected static class TestActivityManagerService extends ActivityManagerService { - private ClientLifecycleManager mLifecycleManager; - private LockTaskController mLockTaskController; - - TestActivityManagerService(Context context) { - super(context); - mSupportsMultiWindow = true; - mSupportsMultiDisplay = true; - mSupportsSplitScreenMultiWindow = true; - mSupportsFreeformWindowManagement = true; - mSupportsPictureInPicture = true; - mWindowManager = WindowTestUtils.getMockWindowManagerService(); - } - - @Override - public ClientLifecycleManager getLifecycleManager() { - if (mLifecycleManager == null) { - return super.getLifecycleManager(); - } - return mLifecycleManager; - } - - public LockTaskController getLockTaskController() { - if (mLockTaskController == null) { - mLockTaskController = spy(super.getLockTaskController()); - } - - return mLockTaskController; - } - - void setLifecycleManager(ClientLifecycleManager manager) { - mLifecycleManager = manager; - } - - @Override - final protected ActivityStackSupervisor createStackSupervisor() { - final ActivityStackSupervisor supervisor = spy(createTestSupervisor()); - final KeyguardController keyguardController = mock(KeyguardController.class); - - // No home stack is set. - doNothing().when(supervisor).moveHomeStackToFront(any()); - doReturn(true).when(supervisor).moveHomeStackTaskToTop(any()); - // Invoked during {@link ActivityStack} creation. - doNothing().when(supervisor).updateUIDsPresentOnDisplay(); - // Always keep things awake. - doReturn(true).when(supervisor).hasAwakeDisplay(); - // Called when moving activity to pinned stack. - doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), anyBoolean()); - // Do not schedule idle timeouts - doNothing().when(supervisor).scheduleIdleTimeoutLocked(any()); - // unit test version does not handle launch wake lock - doNothing().when(supervisor).acquireLaunchWakelock(); - doReturn(keyguardController).when(supervisor).getKeyguardController(); - - supervisor.initialize(); - - return supervisor; - } - - protected ActivityStackSupervisor createTestSupervisor() { - return new TestActivityStackSupervisor(this, mHandlerThread.getLooper()); - } - - @Override - void updateUsageStats(ActivityRecord component, boolean resumed) { - } - } - - /** - * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on - * setup not available in the test environment. Also specifies an injector for - */ - protected static class TestActivityStackSupervisor extends ActivityStackSupervisor { - private ActivityDisplay mDisplay; - private KeyguardController mKeyguardController; - - public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) { - super(service, looper); - mDisplayManager = - (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE); - mWindowManager = prepareMockWindowManager(); - mKeyguardController = mock(KeyguardController.class); - } - - @Override - public void initialize() { - super.initialize(); - mDisplay = spy(new TestActivityDisplay(this, DEFAULT_DISPLAY)); - attachDisplay(mDisplay); - } - - @Override - public KeyguardController getKeyguardController() { - return mKeyguardController; - } - - @Override - ActivityDisplay getDefaultDisplay() { - return mDisplay; - } - - // Just return the current front task. This is called internally so we cannot use spy to mock this out. - @Override - ActivityStack getNextFocusableStackLocked(ActivityStack currentFocus, - boolean ignoreCurrent) { - return mFocusedStack; - } - } - - protected static class TestActivityDisplay extends ActivityDisplay { - - private final ActivityStackSupervisor mSupervisor; - TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { - super(supervisor, displayId); - mSupervisor = supervisor; - } - - @Override - T createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop) { - if (windowingMode == WINDOWING_MODE_PINNED) { - return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) { - @Override - Rect getDefaultPictureInPictureBounds(float aspectRatio) { - return new Rect(50, 50, 100, 100); - } - - @Override - PinnedStackWindowController createStackWindowController(int displayId, - boolean onTop, Rect outBounds) { - return mock(PinnedStackWindowController.class); - } - }; - } else { - return (T) new TestActivityStack( - this, stackId, mSupervisor, windowingMode, activityType, onTop); - } - } - - @Override - protected DisplayWindowController createWindowContainerController() { - return mock(DisplayWindowController.class); - } - } - - private static WindowManagerService prepareMockWindowManager() { - final WindowManagerService service = WindowTestUtils.getMockWindowManagerService(); - - doAnswer((InvocationOnMock invocationOnMock) -> { - final Runnable runnable = invocationOnMock.getArgument(0); - if (runnable != null) { - runnable.run(); - } - return null; - }).when(service).inSurfaceTransaction(any()); - - return service; - } - - /** - * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a - * method is called. Note that its functionality depends on the implementations of the - * construction arguments. - */ - protected static class TestActivityStack - extends ActivityStack { - private int mOnActivityRemovedFromStackCount = 0; - private T mContainerController; - - static final int IS_TRANSLUCENT_UNSET = 0; - static final int IS_TRANSLUCENT_FALSE = 1; - static final int IS_TRANSLUCENT_TRUE = 2; - private int mIsTranslucent = IS_TRANSLUCENT_UNSET; - - static final int SUPPORTS_SPLIT_SCREEN_UNSET = 0; - static final int SUPPORTS_SPLIT_SCREEN_FALSE = 1; - static final int SUPPORTS_SPLIT_SCREEN_TRUE = 2; - private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET; - - TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor, - int windowingMode, int activityType, boolean onTop) { - super(display, stackId, supervisor, windowingMode, activityType, onTop); - } - - @Override - void onActivityRemovedFromStack(ActivityRecord r) { - mOnActivityRemovedFromStackCount++; - super.onActivityRemovedFromStack(r); - } - - // Returns the number of times {@link #onActivityRemovedFromStack} has been called - int onActivityRemovedFromStackInvocationCount() { - return mOnActivityRemovedFromStackCount; - } - - @Override - protected T createStackWindowController(int displayId, boolean onTop, Rect outBounds) { - mContainerController = (T) WindowTestUtils.createMockStackWindowContainerController(); - - // Primary pinned stacks require a non-empty out bounds to be set or else all tasks - // will be moved to the full screen stack. - if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - outBounds.set(0, 0, 100, 100); - } - return mContainerController; - } - - @Override - T getWindowContainerController() { - return mContainerController; - } - - void setIsTranslucent(boolean isTranslucent) { - mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE; - } - - @Override - boolean isStackTranslucent(ActivityRecord starting) { - switch (mIsTranslucent) { - case IS_TRANSLUCENT_TRUE: - return true; - case IS_TRANSLUCENT_FALSE: - return false; - case IS_TRANSLUCENT_UNSET: - default: - return super.isStackTranslucent(starting); - } - } - - void setSupportsSplitScreen(boolean supportsSplitScreen) { - mSupportsSplitScreen = supportsSplitScreen - ? SUPPORTS_SPLIT_SCREEN_TRUE : SUPPORTS_SPLIT_SCREEN_FALSE; - } - - @Override - public boolean supportsSplitScreenWindowingMode() { - switch (mSupportsSplitScreen) { - case SUPPORTS_SPLIT_SCREEN_TRUE: - return true; - case SUPPORTS_SPLIT_SCREEN_FALSE: - return false; - case SUPPORTS_SPLIT_SCREEN_UNSET: - default: - return super.supportsSplitScreenWindowingMode(); - } - } - - @Override - void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, - boolean newTask, boolean keepCurTransition, - ActivityOptions options) { - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java deleted file mode 100644 index 3d11c4c84ff1..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import android.content.Context; -import android.os.Handler; -import android.support.test.InstrumentationRegistry; -import android.support.test.annotation.UiThreadTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.server.AppOpsService; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -/** - * runtest -c com.android.server.am.AppErrorDialogTest frameworks-services - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class AppErrorDialogTest { - - private Context mContext; - private ActivityManagerService mService; - - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - mService = new ActivityManagerService(new ActivityManagerService.Injector() { - @Override - public AppOpsService getAppOpsService(File file, Handler handler) { - return null; - } - - @Override - public Handler getUiHandler(ActivityManagerService service) { - return null; - } - - @Override - public boolean isNetworkRestrictedForUid(int uid) { - return false; - } - }); - } - - @Test - @UiThreadTest - public void testCreateWorks() throws Exception { - AppErrorDialog.Data data = new AppErrorDialog.Data(); - data.proc = new ProcessRecord(null, null, mContext.getApplicationInfo(), "name", 12345); - data.result = new AppErrorResult(); - - AppErrorDialog dialog = new AppErrorDialog(mContext, mService, data); - - dialog.create(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java deleted file mode 100644 index ce88d849a1d4..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.MODE_ERRORED; -import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT; -import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE; -import static android.graphics.Bitmap.Config.ARGB_8888; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.content.Context; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Log; -import android.view.IWindowManager; - -import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Note: Currently, we only support fetching the screenshot for the current application, so the - * screenshot checks are hardcoded accordingly. - * - * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java - */ -@MediumTest -@RunWith(AndroidJUnit4.class) -public class AssistDataRequesterTest extends ActivityTestsBase { - - private static final String TAG = AssistDataRequesterTest.class.getSimpleName(); - - private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true; - private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true; - private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true; - private static final boolean FETCH_DATA = true; - private static final boolean FETCH_SCREENSHOTS = true; - private static final boolean ALLOW_FETCH_DATA = true; - private static final boolean ALLOW_FETCH_SCREENSHOTS = true; - - private static final int TEST_UID = 0; - private static final String TEST_PACKAGE = ""; - - private Context mContext; - private AssistDataRequester mDataRequester; - private Callbacks mCallbacks; - private Object mCallbacksLock; - private Handler mHandler; - private IActivityManager mAm; - private IWindowManager mWm; - private AppOpsManager mAppOpsManager; - - /** - * The requests to fetch assist data are done incrementally from the text thread, and we - * immediately post onto the main thread handler below, which would immediately make the - * callback and decrement the pending counts. In order to assert the pending counts, we defer - * the callbacks on the test-side until after we flip the gate, after which we can drain the - * main thread handler and make assertions on the actual callbacks - */ - private CountDownLatch mGate; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - mAm = mock(IActivityManager.class); - mWm = mock(IWindowManager.class); - mAppOpsManager = mock(AppOpsManager.class); - mContext = InstrumentationRegistry.getContext(); - mHandler = new Handler(Looper.getMainLooper()); - mCallbacksLock = new Object(); - mCallbacks = new Callbacks(); - mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks, - mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT); - - // Gate the continuation of the assist data callbacks until we are ready within the tests - mGate = new CountDownLatch(1); - doAnswer(invocation -> { - mHandler.post(() -> { - try { - mGate.await(10, TimeUnit.SECONDS); - mDataRequester.onHandleAssistData(new Bundle()); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to wait", e); - } - }); - return true; - }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(), - anyBoolean()); - doAnswer(invocation -> { - mHandler.post(() -> { - try { - mGate.await(10, TimeUnit.SECONDS); - mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1, ARGB_8888)); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to wait", e); - } - }); - return true; - }).when(mWm).requestAssistScreenshot(any()); - } - - private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed, - boolean assistScreenshotAllowed) throws Exception { - doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity(); - doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager) - .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString()); - doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager) - .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString()); - } - - @Test - public void testRequestData() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertReceivedDataCount(5, 5, 1, 1); - } - - @Test - public void testEmptyActivities_expectNoCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertReceivedDataCount(0, 0, 0, 0); - } - - @Test - public void testCurrentAppDisallow_expectNullCallbacks() throws Exception { - setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertReceivedDataCount(0, 1, 0, 1); - } - - @Test - public void testProcessPendingData() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mCallbacks.canHandleReceivedData = false; - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertTrue(mDataRequester.getPendingDataCount() == 5); - assertTrue(mDataRequester.getPendingScreenshotCount() == 1); - mGate.countDown(); - waitForIdle(mHandler); - - // Callbacks still not ready to receive, but all pending data is received - assertTrue(mDataRequester.getPendingDataCount() == 0); - assertTrue(mDataRequester.getPendingScreenshotCount() == 0); - assertTrue(mCallbacks.receivedData.isEmpty()); - assertTrue(mCallbacks.receivedScreenshots.isEmpty()); - assertFalse(mCallbacks.requestCompleted); - - mCallbacks.canHandleReceivedData = true; - mDataRequester.processPendingAssistData(); - // Since we are posting the callback for the request-complete, flush the handler as well - mGate.countDown(); - waitForIdle(mHandler); - assertTrue(mCallbacks.receivedData.size() == 5); - assertTrue(mCallbacks.receivedScreenshots.size() == 1); - assertTrue(mCallbacks.requestCompleted); - - // Clear the state and ensure that we only process pending data once - mCallbacks.reset(); - mDataRequester.processPendingAssistData(); - assertTrue(mCallbacks.receivedData.isEmpty()); - assertTrue(mCallbacks.receivedScreenshots.isEmpty()); - } - - @Test - public void testNoFetchData_expectNoDataCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertReceivedDataCount(0, 0, 0, 1); - } - - @Test - public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - // Expect a single null data when the appops is denied - assertReceivedDataCount(0, 1, 0, 1); - } - - @Test - public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), - anyBoolean(), anyBoolean()); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - // Expect a single null data when requestAssistContextExtras() fails - assertReceivedDataCount(0, 1, 0, 1); - } - - @Test - public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertReceivedDataCount(5, 5, 0, 0); - } - - @Test - public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - !CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - // Expect a single null screenshot when the appops is denied - assertReceivedDataCount(5, 5, 0, 1); - } - - @Test - public void testCanNotHandleReceivedData_expectNoCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED, - !CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mCallbacks.canHandleReceivedData = false; - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - mGate.countDown(); - waitForIdle(mHandler); - assertTrue(mCallbacks.receivedData.isEmpty()); - assertTrue(mCallbacks.receivedScreenshots.isEmpty()); - } - - @Test - public void testRequestDataNoneAllowed_expectNullCallbacks() throws Exception { - setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, - CALLER_ASSIST_SCREENSHOT_ALLOWED); - - mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, - !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); - assertReceivedDataCount(0, 1, 0, 1); - } - - private void assertReceivedDataCount(int numPendingData, int numReceivedData, - int numPendingScreenshots, int numReceivedScreenshots) throws Exception { - assertTrue("Expected " + numPendingData + " pending data, got " - + mDataRequester.getPendingDataCount(), - mDataRequester.getPendingDataCount() == numPendingData); - assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got " - + mDataRequester.getPendingScreenshotCount(), - mDataRequester.getPendingScreenshotCount() == numPendingScreenshots); - assertFalse("Expected request NOT completed", mCallbacks.requestCompleted); - mGate.countDown(); - waitForIdle(mHandler); - assertTrue("Expected " + numReceivedData + " data, received " - + mCallbacks.receivedData.size(), - mCallbacks.receivedData.size() == numReceivedData); - assertTrue("Expected " + numReceivedScreenshots + " screenshots, received " - + mCallbacks.receivedScreenshots.size(), - mCallbacks.receivedScreenshots.size() == numReceivedScreenshots); - assertTrue("Expected request completed", mCallbacks.requestCompleted); - } - - private List createActivityList(int size) { - ArrayList activities = new ArrayList<>(); - for (int i = 0; i < size; i++) { - activities.add(mock(IBinder.class)); - } - return activities; - } - - public void waitForIdle(Handler h) throws Exception { - if (Looper.myLooper() == h.getLooper()) { - throw new RuntimeException("This method can not be called from the waiting looper"); - } - CountDownLatch latch = new CountDownLatch(1); - h.post(() -> latch.countDown()); - latch.await(2, TimeUnit.SECONDS); - } - - private class Callbacks implements AssistDataRequesterCallbacks { - - boolean canHandleReceivedData = true; - boolean requestCompleted = false; - ArrayList receivedData = new ArrayList<>(); - ArrayList receivedScreenshots = new ArrayList<>(); - - void reset() { - canHandleReceivedData = true; - receivedData.clear(); - receivedScreenshots.clear(); - } - - @Override - public boolean canHandleReceivedAssistDataLocked() { - return canHandleReceivedData; - } - - @Override - public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) { - receivedData.add(data); - } - - @Override - public void onAssistScreenshotReceivedLocked(Bitmap screenshot) { - receivedScreenshots.add(screenshot); - } - - @Override - public void onAssistRequestCompleted() { - mHandler.post(() -> { - try { - mGate.await(10, TimeUnit.SECONDS); - requestCompleted = true; - } catch (InterruptedException e) { - Log.e(TAG, "Failed to wait", e); - } - }); - } - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java deleted file mode 100644 index ef6d5e81b472..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.android.server.am; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.app.IApplicationThread; -import android.app.servertransaction.ClientTransaction; -import android.os.Binder; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class ClientLifecycleManagerTests { - - @Test - public void testScheduleAndRecycleBinderClientTransaction() throws Exception { - ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class), - new Binder())); - - ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); - clientLifecycleManager.scheduleTransaction(item); - - verify(item, times(1)).recycle(); - } - - @Test - public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception { - ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class), - new Binder())); - - ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); - clientLifecycleManager.scheduleTransaction(item); - - verify(item, times(0)).recycle(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java deleted file mode 100644 index da30c11be81a..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static com.android.server.am.ActivityManagerService.Injector; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.test.mock.MockContentResolver; - -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.AppOpsService; - -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.File; - -/** - * Test class for {@link CoreSettingsObserver}. - * - * To run the tests, use - * - * runtest -c com.android.server.am.CoreSettingsObserverTest frameworks-services - * - * or the following steps: - * - * Build: m FrameworksServicesTests - * Install: adb install -r \ - * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * Run: adb shell am instrument -e class com.android.server.am.CoreSettingsObserverTest -w \ - * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class CoreSettingsObserverTest { - private static final String TEST_SETTING_SECURE_INT = "secureInt"; - private static final String TEST_SETTING_GLOBAL_FLOAT = "globalFloat"; - private static final String TEST_SETTING_SYSTEM_STRING = "systemString"; - - private static final int TEST_INT = 111; - private static final float TEST_FLOAT = 3.14f; - private static final String TEST_STRING = "testString"; - - private ActivityManagerService mAms; - @Mock private Context mContext; - - private MockContentResolver mContentResolver; - private CoreSettingsObserver mCoreSettingsObserver; - - @BeforeClass - public static void setupOnce() { - FakeSettingsProvider.clearSettingsProvider(); - CoreSettingsObserver.sSecureSettingToTypeMap.put(TEST_SETTING_SECURE_INT, int.class); - CoreSettingsObserver.sGlobalSettingToTypeMap.put(TEST_SETTING_GLOBAL_FLOAT, float.class); - CoreSettingsObserver.sSystemSettingToTypeMap.put(TEST_SETTING_SYSTEM_STRING, String.class); - } - - @AfterClass - public static void tearDownOnce() { - FakeSettingsProvider.clearSettingsProvider(); - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - final Context originalContext = InstrumentationRegistry.getContext(); - when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); - mContentResolver = new MockContentResolver(mContext); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContext.getContentResolver()).thenReturn(mContentResolver); - - mAms = new ActivityManagerService(new TestInjector()); - mCoreSettingsObserver = new CoreSettingsObserver(mAms); - } - - @Test - public void testPopulateSettings() { - Settings.Secure.putInt(mContentResolver, TEST_SETTING_SECURE_INT, TEST_INT); - Settings.Global.putFloat(mContentResolver, TEST_SETTING_GLOBAL_FLOAT, TEST_FLOAT); - Settings.System.putString(mContentResolver, TEST_SETTING_SYSTEM_STRING, TEST_STRING); - - final Bundle settingsBundle = getPopulatedBundle(); - - assertEquals("Unexpected value of " + TEST_SETTING_SECURE_INT, - TEST_INT, settingsBundle.getInt(TEST_SETTING_SECURE_INT)); - assertEquals("Unexpected value of " + TEST_SETTING_GLOBAL_FLOAT, - TEST_FLOAT, settingsBundle.getFloat(TEST_SETTING_GLOBAL_FLOAT), 0); - assertEquals("Unexpected value of " + TEST_SETTING_SYSTEM_STRING, - TEST_STRING, settingsBundle.getString(TEST_SETTING_SYSTEM_STRING)); - } - - @Test - public void testPopulateSettings_settingNotSet() { - final Bundle settingsBundle = getPopulatedBundle(); - - assertFalse("Bundle should not contain " + TEST_SETTING_SECURE_INT, - settingsBundle.containsKey(TEST_SETTING_SECURE_INT)); - assertFalse("Bundle should not contain " + TEST_SETTING_GLOBAL_FLOAT, - settingsBundle.containsKey(TEST_SETTING_GLOBAL_FLOAT)); - assertFalse("Bundle should not contain " + TEST_SETTING_SYSTEM_STRING, - settingsBundle.containsKey(TEST_SETTING_SYSTEM_STRING)); - } - - private Bundle getPopulatedBundle() { - final Bundle settingsBundle = new Bundle(); - mCoreSettingsObserver.populateSettings(settingsBundle, - CoreSettingsObserver.sGlobalSettingToTypeMap); - mCoreSettingsObserver.populateSettings(settingsBundle, - CoreSettingsObserver.sSecureSettingToTypeMap); - mCoreSettingsObserver.populateSettings(settingsBundle, - CoreSettingsObserver.sSystemSettingToTypeMap); - return settingsBundle; - } - - private class TestInjector extends Injector { - @Override - public Context getContext() { - return mContext; - } - - public AppOpsService getAppOpsService(File file, Handler handler) { - return null; - } - - @Override - public Handler getUiHandler(ActivityManagerService service) { - return null; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java deleted file mode 100644 index d9b3e1c0173b..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import android.content.ContentResolver; -import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.test.mock.MockContentResolver; - -import com.android.internal.util.Preconditions; -import com.android.internal.util.test.FakeSettingsProvider; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashMap; -import java.util.Map; - -/** - * Tests for {@link GlobalSettingsToPropertiesMapper} - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class GlobalSettingsToPropertiesMapperTest { - private static final String[][] TEST_MAPPING = new String[][] { - {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"} - }; - - private TestMapper mTestMapper; - private MockContentResolver mMockContentResolver; - - @Before - public void setup() { - // Use FakeSettingsProvider to not affect global state - mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext()); - mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - mTestMapper = new TestMapper(mMockContentResolver); - } - - @Test - public void testUpdatePropertiesFromGlobalSettings() { - Settings.Global.putString(mMockContentResolver, - Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue"); - - mTestMapper.updatePropertiesFromGlobalSettings(); - String propValue = mTestMapper.systemPropertiesGet("TestProperty"); - Assert.assertEquals("testValue", propValue); - - Settings.Global.putString(mMockContentResolver, - Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2"); - mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, - "TestProperty"); - propValue = mTestMapper.systemPropertiesGet("TestProperty"); - Assert.assertEquals("testValue2", propValue); - - Settings.Global.putString(mMockContentResolver, - Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null); - mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, - "TestProperty"); - propValue = mTestMapper.systemPropertiesGet("TestProperty"); - Assert.assertEquals("", propValue); - } - - @Test - public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() { - // Test that empty property will not not be set if setting is not set - mTestMapper.updatePropertiesFromGlobalSettings(); - String propValue = mTestMapper.systemPropertiesGet("TestProperty"); - Assert.assertNull("Property should not be set if setting is null", propValue); - } - - private static class TestMapper extends GlobalSettingsToPropertiesMapper { - private final Map mProps = new HashMap<>(); - - TestMapper(ContentResolver contentResolver) { - super(contentResolver, TEST_MAPPING); - } - - @Override - protected String systemPropertiesGet(String key) { - Preconditions.checkNotNull(key); - return mProps.get(key); - } - - @Override - protected void systemPropertiesSet(String key, String value) { - Preconditions.checkNotNull(value); - Preconditions.checkNotNull(key); - mProps.put(key, value); - } - } - -} - diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java deleted file mode 100644 index 161c2875bf49..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/LaunchParamsControllerTests.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import android.app.ActivityOptions; -import android.content.pm.ActivityInfo.WindowLayout; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.server.am.LaunchParamsController.LaunchParams; -import org.junit.runner.RunWith; -import org.junit.Before; -import org.junit.Test; - -import com.android.server.am.LaunchParamsController.LaunchParamsModifier; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE; -import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; -import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -/** - * Tests for exercising {@link LaunchParamsController}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:LaunchParamsControllerTests - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class LaunchParamsControllerTests extends ActivityTestsBase { - private ActivityManagerService mService; - private LaunchParamsController mController; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - mService = createActivityManagerService(); - mController = new LaunchParamsController(mService); - } - - /** - * Makes sure positioners get values passed to controller. - */ - @Test - public void testArgumentPropagation() { - final LaunchParamsModifier - positioner = mock(LaunchParamsModifier.class); - mController.registerModifier(positioner); - - final ActivityRecord record = new ActivityBuilder(mService).build(); - final ActivityRecord source = new ActivityBuilder(mService).build(); - final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0); - final ActivityOptions options = mock(ActivityOptions.class); - - mController.calculate(record.getTask(), layout, record, source, options, - new LaunchParams()); - verify(positioner, times(1)).onCalculate(eq(record.getTask()), eq(layout), eq(record), - eq(source), eq(options), any(), any()); - } - - /** - * Ensures positioners further down the chain are not called when RESULT_DONE is returned. - */ - @Test - public void testEarlyExit() { - final LaunchParamsModifier - ignoredPositioner = mock(LaunchParamsModifier.class); - final LaunchParamsModifier earlyExitPositioner = - (task, layout, activity, source, options, currentParams, outParams) -> RESULT_DONE; - - mController.registerModifier(ignoredPositioner); - mController.registerModifier(earlyExitPositioner); - - mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, - null /*source*/, null /*options*/, new LaunchParams()); - verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(), - any(), any()); - } - - /** - * Ensures that positioners are called in the correct order. - */ - @Test - public void testRegistration() { - LaunchParamsModifier earlyExitPositioner = - new InstrumentedPositioner(RESULT_DONE, new LaunchParams()); - - final LaunchParamsModifier firstPositioner = spy(earlyExitPositioner); - - mController.registerModifier(firstPositioner); - - mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, - null /*source*/, null /*options*/, new LaunchParams()); - verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), - any()); - - final LaunchParamsModifier secondPositioner = spy(earlyExitPositioner); - - mController.registerModifier(secondPositioner); - - mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, - null /*source*/, null /*options*/, new LaunchParams()); - verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), - any()); - verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), - any()); - } - - /** - * Makes sure positioners further down the registration chain are called. - */ - @Test - public void testPassThrough() { - final LaunchParamsModifier - positioner1 = mock(LaunchParamsModifier.class); - final LaunchParams params = new LaunchParams(); - params.mWindowingMode = WINDOWING_MODE_FREEFORM; - params.mBounds.set(0, 0, 30, 20); - params.mPreferredDisplayId = 3; - - final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE, - params); - - mController.registerModifier(positioner1); - mController.registerModifier(positioner2); - - mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, - null /*options*/, new LaunchParams()); - - verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(), - eq(positioner2.getLaunchParams()), any()); - } - - /** - * Ensures skipped results are not propagated. - */ - @Test - public void testSkip() { - final LaunchParams params1 = new LaunchParams(); - params1.mBounds.set(0, 0, 10, 10); - final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1); - - final LaunchParams params2 = new LaunchParams(); - params2.mBounds.set(0, 0, 20, 30); - final InstrumentedPositioner positioner2 = - new InstrumentedPositioner(RESULT_CONTINUE, params2); - - mController.registerModifier(positioner1); - mController.registerModifier(positioner2); - - final LaunchParams - result = new LaunchParams(); - - mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, - null /*options*/, result); - - assertEquals(result, positioner2.getLaunchParams()); - } - - /** - * Ensures that {@link LaunchParamsModifier} requests specifying display id during - * layout are honored. - */ - @Test - public void testLayoutTaskPreferredDisplayChange() { - final LaunchParams params = new LaunchParams(); - params.mPreferredDisplayId = 2; - final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); - final TaskRecord task = new TaskBuilder(mService.mStackSupervisor).build(); - - mController.registerModifier(positioner); - - doNothing().when(mService).moveStackToDisplay(anyInt(), anyInt()); - mController.layoutTask(task, null /* windowLayout */); - verify(mService, times(1)).moveStackToDisplay(eq(task.getStackId()), - eq(params.mPreferredDisplayId)); - } - - /** - * Ensures that {@link LaunchParamsModifier} requests specifying windowingMode during - * layout are honored. - */ - @Test - public void testLayoutTaskWindowingModeChange() { - final LaunchParams params = new LaunchParams(); - final int windowingMode = WINDOWING_MODE_FREEFORM; - params.mWindowingMode = windowingMode; - final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); - final TaskRecord task = new TaskBuilder(mService.mStackSupervisor).build(); - - mController.registerModifier(positioner); - - final int beforeWindowMode = task.getStack().getWindowingMode(); - assertNotEquals(beforeWindowMode, windowingMode); - - mController.layoutTask(task, null /* windowLayout */); - - final int afterWindowMode = task.getStack().getWindowingMode(); - assertEquals(afterWindowMode, windowingMode); - } - - public static class InstrumentedPositioner implements - LaunchParamsModifier { - - final private int mReturnVal; - final private LaunchParams mParams; - - InstrumentedPositioner(int returnVal, LaunchParams params) { - mReturnVal = returnVal; - mParams = params; - } - - @Override - public int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity, - ActivityRecord source, ActivityOptions options, - LaunchParams currentParams, LaunchParams outParams) { - outParams.set(mParams); - return mReturnVal; - } - - LaunchParams getLaunchParams() { - return mParams; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java deleted file mode 100644 index f46d712df65b..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java +++ /dev/null @@ -1,680 +0,0 @@ -/* - * Copyright 2017, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; -import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; -import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; -import static android.app.StatusBarManager.DISABLE2_MASK; -import static android.app.StatusBarManager.DISABLE2_NONE; -import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; -import static android.app.StatusBarManager.DISABLE_HOME; -import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS; -import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; -import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; -import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD; -import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE; -import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; -import static android.os.Process.SYSTEM_UID; -import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; - -import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_LOCKED; -import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_PINNED; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import android.app.StatusBarManager; -import android.app.admin.DevicePolicyManager; -import android.app.admin.IDevicePolicyManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.UserHandle; -import android.platform.test.annotations.Presubmit; -import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.telecom.TelecomManager; -import android.testing.DexmakerShareClassLoaderRule; -import android.util.Pair; - -import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.widget.LockPatternUtils; -import com.android.server.LocalServices; -import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.wm.WindowManagerService; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.verification.VerificationMode; - -/** - * Unit tests for {@link LockTaskController}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.am.LockTaskControllerTest - */ -@Presubmit -@SmallTest -public class LockTaskControllerTest { - private static final String TEST_PACKAGE_NAME = "com.test.package"; - private static final String TEST_PACKAGE_NAME_2 = "com.test.package2"; - private static final String TEST_CLASS_NAME = ".TestClass"; - private static final int TEST_USER_ID = 123; - private static final int TEST_UID = 10467; - - @Rule - public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = - new DexmakerShareClassLoaderRule(); - - @Mock private ActivityStackSupervisor mSupervisor; - @Mock private IDevicePolicyManager mDevicePolicyManager; - @Mock private IStatusBarService mStatusBarService; - @Mock private WindowManagerService mWindowManager; - @Mock private LockPatternUtils mLockPatternUtils; - @Mock private StatusBarManagerInternal mStatusBarManagerInternal; - @Mock private TelecomManager mTelecomManager; - @Mock private RecentTasks mRecentTasks; - - private LockTaskController mLockTaskController; - private Context mContext; - private String mLockToAppSetting; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mContext = InstrumentationRegistry.getTargetContext(); - mLockToAppSetting = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.LOCK_TO_APP_EXIT_LOCKED); - - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - mSupervisor.mRecentTasks = mRecentTasks; - - mLockTaskController = new LockTaskController(mContext, mSupervisor, - new ImmediatelyExecuteHandler()); - mLockTaskController.setWindowManager(mWindowManager); - mLockTaskController.mStatusBarService = mStatusBarService; - mLockTaskController.mDevicePolicyManager = mDevicePolicyManager; - mLockTaskController.mTelecomManager = mTelecomManager; - mLockTaskController.mLockPatternUtils = mLockPatternUtils; - - LocalServices.removeServiceForTest(StatusBarManagerInternal.class); - LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); - } - - @After - public void tearDown() throws Exception { - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, mLockToAppSetting); - } - - @Test - public void testPreconditions() { - // GIVEN nothing has happened - - // THEN current lock task mode should be NONE - assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); - } - - @Test - public void testStartLockTaskMode_once() throws Exception { - // GIVEN a task record with whitelisted auth - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - - // WHEN calling setLockTaskMode for LOCKED mode without resuming - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // THEN the lock task mode state should be LOCKED - assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); - // THEN the task should be locked - assertTrue(mLockTaskController.isTaskLocked(tr)); - - // THEN lock task mode should be started - verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); - } - - @Test - public void testStartLockTaskMode_twice() throws Exception { - // GIVEN two task records with whitelisted auth - TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - - // WHEN calling setLockTaskMode for LOCKED mode on both tasks - mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); - mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); - - // THEN the lock task mode state should be LOCKED - assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); - // THEN neither of the tasks should be able to move to back of stack - assertTrue(mLockTaskController.isTaskLocked(tr1)); - assertTrue(mLockTaskController.isTaskLocked(tr2)); - - // THEN lock task mode should be started - verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); - } - - @Test - public void testStartLockTaskMode_pinningRequest() throws Exception { - // GIVEN a task record that is not whitelisted, i.e. with pinned auth - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); - - // WHEN calling startLockTaskMode - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // THEN a pinning request should be shown - verify(mStatusBarManagerInternal).showScreenPinningRequest(anyInt()); - } - - @Test - public void testStartLockTaskMode_pinnedBySystem() throws Exception { - // GIVEN a task record with pinned auth - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); - - // WHEN the system calls startLockTaskMode - mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID); - - // THEN the lock task mode state should be PINNED - assertEquals(LOCK_TASK_MODE_PINNED, mLockTaskController.getLockTaskModeState()); - // THEN the task should be locked - assertTrue(mLockTaskController.isTaskLocked(tr)); - - // THEN lock task mode should be started - verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE); - // THEN screen pinning toast should be shown - verify(mStatusBarService).showPinningEnterExitToast(true /* entering */); - } - - @Test - public void testLockTaskViolation() throws Exception { - // GIVEN one task record with whitelisted auth that is in lock task mode - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // THEN it's not a lock task violation to try and launch this task without clearing - assertFalse(mLockTaskController.isLockTaskModeViolation(tr, false)); - - // THEN it's a lock task violation to launch another task that is not whitelisted - assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord( - TaskRecord.LOCK_TASK_AUTH_PINNABLE))); - // THEN it's a lock task violation to launch another task that is disallowed from lock task - assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord( - TaskRecord.LOCK_TASK_AUTH_DONT_LOCK))); - - // THEN it's no a lock task violation to launch another task that is whitelisted - assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( - TaskRecord.LOCK_TASK_AUTH_WHITELISTED))); - assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( - TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE))); - // THEN it's not a lock task violation to launch another task that is priv launchable - assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( - TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV))); - } - - @Test - public void testLockTaskViolation_emergencyCall() throws Exception { - // GIVEN one task record with whitelisted auth that is in lock task mode - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // GIVEN tasks necessary for emergency calling - TaskRecord keypad = getTaskRecord(new Intent().setComponent(EMERGENCY_DIALER_COMPONENT), - TaskRecord.LOCK_TASK_AUTH_PINNABLE); - TaskRecord callAction = getTaskRecord(new Intent(Intent.ACTION_CALL_EMERGENCY), - TaskRecord.LOCK_TASK_AUTH_PINNABLE); - TaskRecord dialer = getTaskRecord("com.example.dialer", TaskRecord.LOCK_TASK_AUTH_PINNABLE); - when(mTelecomManager.getSystemDialerPackage()) - .thenReturn(dialer.intent.getComponent().getPackageName()); - - // GIVEN keyguard is allowed for lock task mode - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD); - - // THEN the above tasks should all be allowed - assertFalse(mLockTaskController.isLockTaskModeViolation(keypad)); - assertFalse(mLockTaskController.isLockTaskModeViolation(callAction)); - assertFalse(mLockTaskController.isLockTaskModeViolation(dialer)); - - // GIVEN keyguard is disallowed for lock task mode (default) - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE); - - // THEN the above tasks should all be blocked - assertTrue(mLockTaskController.isLockTaskModeViolation(keypad)); - assertTrue(mLockTaskController.isLockTaskModeViolation(callAction)); - assertTrue(mLockTaskController.isLockTaskModeViolation(dialer)); - } - - @Test - public void testStopLockTaskMode() throws Exception { - // GIVEN one task record with whitelisted auth that is in lock task mode - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // WHEN the same caller calls stopLockTaskMode - mLockTaskController.stopLockTaskMode(tr, false, TEST_UID); - - // THEN the lock task mode should be NONE - assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); - // THEN the task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr)); - // THEN lock task mode should have been finished - verifyLockTaskStopped(times(1)); - } - - @Test(expected = SecurityException.class) - public void testStopLockTaskMode_differentCaller() throws Exception { - // GIVEN one task record with whitelisted auth that is in lock task mode - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // WHEN a different caller calls stopLockTaskMode - mLockTaskController.stopLockTaskMode(tr, false, TEST_UID + 1); - - // THEN security exception should be thrown, because different caller tried to unlock - } - - @Test - public void testStopLockTaskMode_systemCaller() throws Exception { - // GIVEN one task record with whitelisted auth that is in lock task mode - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // WHEN system calls stopLockTaskMode - mLockTaskController.stopLockTaskMode(tr, true, SYSTEM_UID); - - // THEN lock task mode should still be active - assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); - } - - @Test - public void testStopLockTaskMode_twoTasks() throws Exception { - // GIVEN two task records with whitelisted auth that is in lock task mode - TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); - mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); - - // WHEN calling stopLockTaskMode - mLockTaskController.stopLockTaskMode(tr2, false, TEST_UID); - - // THEN the lock task mode should still be active - assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); - // THEN the first task should still be locked - assertTrue(mLockTaskController.isTaskLocked(tr1)); - // THEN the top task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr2)); - // THEN lock task mode should not have been finished - verifyLockTaskStopped(never()); - } - - @Test - public void testStopLockTaskMode_rootTask() throws Exception { - // GIVEN two task records with whitelisted auth that is in lock task mode - TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); - mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); - - // WHEN calling stopLockTaskMode on the root task - mLockTaskController.stopLockTaskMode(tr1, false, TEST_UID); - - // THEN the lock task mode should be inactive - assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); - // THEN the first task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr1)); - // THEN the top task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr2)); - // THEN lock task mode should be finished - verifyLockTaskStopped(times(1)); - } - - @Test - public void testStopLockTaskMode_pinned() throws Exception { - // GIVEN one task records that is in pinned mode - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); - mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID); - // GIVEN that the keyguard is required to show after unlocking - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1); - - // reset invocation counter - reset(mStatusBarService); - - // WHEN calling stopLockTask - mLockTaskController.stopLockTaskMode(null, true, SYSTEM_UID); - - // THEN the lock task mode should no longer be active - assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); - // THEN the task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr)); - // THEN lock task mode should have been finished - verifyLockTaskStopped(times(1)); - // THEN the keyguard should be shown - verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL); - // THEN screen pinning toast should be shown - verify(mStatusBarService).showPinningEnterExitToast(false /* entering */); - } - - @Test - public void testClearLockedTasks() throws Exception { - // GIVEN two task records with whitelisted auth that is in lock task mode - TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); - mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); - - // WHEN calling stopLockTaskMode on the root task - mLockTaskController.clearLockedTasks("testClearLockedTasks"); - - // THEN the lock task mode should be inactive - assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); - // THEN the first task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr1)); - // THEN the top task should no longer be locked - assertFalse(mLockTaskController.isTaskLocked(tr2)); - // THEN lock task mode should be finished - verifyLockTaskStopped(times(1)); - } - - @Test - public void testUpdateLockTaskPackages() throws Exception { - String[] whitelist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2}; - String[] whitelist2 = {TEST_PACKAGE_NAME}; - - // No package is whitelisted initially - for (String pkg : whitelist1) { - assertFalse("Package shouldn't be whitelisted: " + pkg, - mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg)); - assertFalse("Package shouldn't be whitelisted for user 0: " + pkg, - mLockTaskController.isPackageWhitelisted(0, pkg)); - } - - // Apply whitelist - mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist1); - - // Assert the whitelist is applied to the correct user - for (String pkg : whitelist1) { - assertTrue("Package should be whitelisted: " + pkg, - mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg)); - assertFalse("Package shouldn't be whitelisted for user 0: " + pkg, - mLockTaskController.isPackageWhitelisted(0, pkg)); - } - - // Update whitelist - mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist2); - - // Assert the new whitelist is applied - assertTrue("Package should remain whitelisted: " + TEST_PACKAGE_NAME, - mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME)); - assertFalse("Package should no longer be whitelisted: " + TEST_PACKAGE_NAME_2, - mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME_2)); - } - - @Test - public void testUpdateLockTaskPackages_taskRemoved() throws Exception { - // GIVEN two tasks which are whitelisted initially - TaskRecord tr1 = getTaskRecordForUpdate(TEST_PACKAGE_NAME, true); - TaskRecord tr2 = getTaskRecordForUpdate(TEST_PACKAGE_NAME_2, false); - String[] whitelist = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2}; - mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); - - // GIVEN the tasks are launched into LockTask mode - mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); - mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); - assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); - assertTrue(mLockTaskController.isTaskLocked(tr1)); - assertTrue(mLockTaskController.isTaskLocked(tr2)); - verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); - - // WHEN removing one package from whitelist - whitelist = new String[] {TEST_PACKAGE_NAME}; - mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); - - // THEN the task running that package should be stopped - verify(tr2).performClearTaskLocked(); - assertFalse(mLockTaskController.isTaskLocked(tr2)); - // THEN the other task should remain locked - assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); - assertTrue(mLockTaskController.isTaskLocked(tr1)); - verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); - - // WHEN removing the last package from whitelist - whitelist = new String[] {}; - mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); - - // THEN the last task should be cleared, and the system should quit LockTask mode - verify(tr1).performClearTaskLocked(); - assertFalse(mLockTaskController.isTaskLocked(tr1)); - assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); - verifyLockTaskStopped(times(1)); - } - - @Test - public void testUpdateLockTaskFeatures() throws Exception { - // GIVEN a locked task - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // THEN lock task mode should be started with default status bar masks - verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); - - // reset invocation counter - reset(mStatusBarService); - - // WHEN home button is enabled for lock task mode - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_HOME); - - // THEN status bar should be updated to reflect this change - int expectedFlags = STATUS_BAR_MASK_LOCKED - & ~DISABLE_HOME; - int expectedFlags2 = DISABLE2_MASK; - verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class), - eq(mContext.getPackageName())); - verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class), - eq(mContext.getPackageName())); - - // reset invocation counter - reset(mStatusBarService); - - // WHEN notifications are enabled for lock task mode - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NOTIFICATIONS); - - // THEN status bar should be updated to reflect this change - expectedFlags = STATUS_BAR_MASK_LOCKED - & ~DISABLE_NOTIFICATION_ICONS - & ~DISABLE_NOTIFICATION_ALERTS; - expectedFlags2 = DISABLE2_MASK - & ~DISABLE2_NOTIFICATION_SHADE; - verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class), - eq(mContext.getPackageName())); - verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class), - eq(mContext.getPackageName())); - } - - @Test - public void testUpdateLockTaskFeatures_differentUser() throws Exception { - // GIVEN a locked task - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // THEN lock task mode should be started with default status bar masks - verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); - - // reset invocation counter - reset(mStatusBarService); - - // WHEN home button is enabled for lock task mode for another user - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID + 1, LOCK_TASK_FEATURE_HOME); - - // THEN status bar shouldn't change - verify(mStatusBarService, never()).disable(anyInt(), any(IBinder.class), - eq(mContext.getPackageName())); - verify(mStatusBarService, never()).disable2(anyInt(), any(IBinder.class), - eq(mContext.getPackageName())); - } - - @Test - public void testUpdateLockTaskFeatures_keyguard() throws Exception { - // GIVEN a locked task - TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); - mLockTaskController.startLockTaskMode(tr, false, TEST_UID); - - // THEN keyguard should be disabled - verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); - - // WHEN keyguard is enabled for lock task mode - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD); - - // THEN keyguard should be enabled - verify(mWindowManager).reenableKeyguard(any(IBinder.class)); - - // WHEN keyguard is disabled again for lock task mode - mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE); - - // THEN keyguard should be disabled - verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString()); - } - - @Test - public void testGetStatusBarDisableFlags() { - // Note that we don't enumerate all StatusBarManager flags, but only choose a subset to test - - // WHEN nothing is enabled - Pair flags = mLockTaskController.getStatusBarDisableFlags( - LOCK_TASK_FEATURE_NONE); - // THEN unsupported feature flags should still be untouched - assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0); - // THEN everything else should be disabled - assertTrue((StatusBarManager.DISABLE_CLOCK & flags.first) != 0); - assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0); - - // WHEN only home button is enabled - flags = mLockTaskController.getStatusBarDisableFlags( - LOCK_TASK_FEATURE_HOME); - // THEN unsupported feature flags should still be untouched - assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0); - // THEN home button should indeed be enabled - assertTrue((StatusBarManager.DISABLE_HOME & flags.first) == 0); - // THEN other feature flags should remain disabled - assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) != 0); - - // WHEN only global actions menu and notifications are enabled - flags = mLockTaskController.getStatusBarDisableFlags( - DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS - | DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS); - // THEN unsupported feature flags should still be untouched - assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0); - // THEN notifications should be enabled - assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ICONS & flags.first) == 0); - assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ALERTS & flags.first) == 0); - assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) == 0); - // THEN global actions should be enabled - assertTrue((StatusBarManager.DISABLE2_GLOBAL_ACTIONS & flags.second) == 0); - // THEN quick settings should still be disabled - assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0); - } - - private TaskRecord getTaskRecord(int lockTaskAuth) { - return getTaskRecord(TEST_PACKAGE_NAME, lockTaskAuth); - } - - private TaskRecord getTaskRecord(String pkg, int lockTaskAuth) { - final Intent intent = new Intent() - .setComponent(ComponentName.createRelative(pkg, TEST_CLASS_NAME)); - return getTaskRecord(intent, lockTaskAuth); - } - - private TaskRecord getTaskRecord(Intent intent, int lockTaskAuth) { - TaskRecord tr = mock(TaskRecord.class); - tr.mLockTaskAuth = lockTaskAuth; - tr.intent = intent; - tr.userId = TEST_USER_ID; - return tr; - } - - /** - * @param isAppAware {@code true} if the app has marked if_whitelisted in its manifest - */ - private TaskRecord getTaskRecordForUpdate(String pkg, boolean isAppAware) { - final int authIfWhitelisted = isAppAware - ? TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE - : TaskRecord.LOCK_TASK_AUTH_WHITELISTED; - TaskRecord tr = getTaskRecord(pkg, authIfWhitelisted); - doAnswer((invocation) -> { - boolean isWhitelisted = - mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg); - tr.mLockTaskAuth = isWhitelisted - ? authIfWhitelisted - : TaskRecord.LOCK_TASK_AUTH_PINNABLE; - return null; - }).when(tr).setLockTaskAuth(); - return tr; - } - - private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception { - // THEN the keyguard should have been disabled - verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); - // THEN the status bar should have been disabled - verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class), - eq(mContext.getPackageName())); - verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class), - eq(mContext.getPackageName())); - // THEN recents should have been notified - verify(mRecentTasks).onLockTaskModeStateChanged(anyInt(), eq(TEST_USER_ID)); - // THEN the DO/PO should be informed about the operation - verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME, - TEST_USER_ID); - } - - private void verifyLockTaskStopped(VerificationMode mode) throws Exception { - // THEN the keyguard should have been disabled - verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class)); - // THEN the status bar should have been disabled - verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE), - any(IBinder.class), eq(mContext.getPackageName())); - verify(mStatusBarService, mode).disable2(eq(StatusBarManager.DISABLE2_NONE), - any(IBinder.class), eq(mContext.getPackageName())); - // THEN the DO/PO should be informed about the operation - verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID); - } - - /** - * Special handler implementation that executes any message / runnable posted immediately on the - * thread that it's posted on rather than enqueuing them on its looper. - */ - private static class ImmediatelyExecuteHandler extends Handler { - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - if (msg.getCallback() != null) { - msg.getCallback().run(); - } - return true; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java deleted file mode 100644 index 5518ca5bd8cc..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg; -import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs; -import static com.android.server.am.MemoryStatUtil.MemoryStat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class MemoryStatUtilTest { - private String MEMORY_STAT_CONTENTS = String.join( - "\n", - "cache 96", // keep different from total_cache to catch reading wrong value - "rss 97", // keep different from total_rss to catch reading wrong value - "rss_huge 0", - "mapped_file 524288", - "writeback 0", - "swap 95", // keep different from total_rss to catch reading wrong value - "pgpgin 16717", - "pgpgout 5037", - "pgfault 99", // keep different from total_pgfault to catch reading wrong value - "pgmajfault 98", // keep different from total_pgmajfault to catch reading wrong value - "inactive_anon 503808", - "active_anon 46309376", - "inactive_file 876544", - "active_file 81920", - "unevictable 0", - "hierarchical_memory_limit 18446744073709551615", - "hierarchical_memsw_limit 18446744073709551615", - "total_cache 4", - "total_rss 3", - "total_rss_huge 0", - "total_mapped_file 524288", - "total_writeback 0", - "total_swap 5", - "total_pgpgin 16717", - "total_pgpgout 5037", - "total_pgfault 1", - "total_pgmajfault 2", - "total_inactive_anon 503808", - "total_active_anon 46309376", - "total_inactive_file 876544", - "total_active_file 81920", - "total_unevictable 0"); - - private String PROC_STAT_CONTENTS = String.join( - " ", - "1040", - "(system_server)", - "S", - "544", - "544", - "0", - "0", - "-1", - "1077936448", - "1", // this is pgfault - "0", - "2", // this is pgmajfault - "0", - "44533", - "13471", - "0", - "0", - "18", - "-2", - "117", - "0", - "2206", - "1257177088", - "3", // this is rss in bytes - "4294967295", - "2936971264", - "2936991289", - "3198888320", - "3198879848", - "2903927664", - "0", - "4612", - "0", - "1073775864", - "4294967295", - "0", - "0", - "17", - "0", - "0", - "0", - "0", - "0", - "0", - "2936999088", - "2936999936", - "2958692352", - "3198888595", - "3198888671", - "3198888671", - "3198889956", - "0"); - - @Test - public void testParseMemoryStatFromMemcg_parsesCorrectValues() throws Exception { - MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS); - assertEquals(stat.pgfault, 1); - assertEquals(stat.pgmajfault, 2); - assertEquals(stat.rssInBytes, 3); - assertEquals(stat.cacheInBytes, 4); - assertEquals(stat.swapInBytes, 5); - } - - @Test - public void testParseMemoryStatFromMemcg_emptyMemoryStatContents() throws Exception { - MemoryStat stat = parseMemoryStatFromMemcg(""); - assertNull(stat); - - stat = parseMemoryStatFromMemcg(null); - assertNull(stat); - } - - @Test - public void testParseMemoryStatFromProcfs_parsesCorrectValues() throws Exception { - MemoryStat stat = parseMemoryStatFromProcfs(PROC_STAT_CONTENTS); - assertEquals(1, stat.pgfault); - assertEquals(2, stat.pgmajfault); - assertEquals(3, stat.rssInBytes); - assertEquals(0, stat.cacheInBytes); - assertEquals(0, stat.swapInBytes); - } - - @Test - public void testParseMemoryStatFromProcfs_emptyContents() throws Exception { - MemoryStat stat = parseMemoryStatFromProcfs(""); - assertNull(stat); - - stat = parseMemoryStatFromProcfs(null); - assertNull(stat); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java deleted file mode 100644 index 2baf9952cb9e..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.os.Handler; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.ArrayMap; -import android.view.RemoteAnimationAdapter; - -import com.android.server.testutils.OffsettableClock; -import com.android.server.testutils.TestHandler; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * atest PendingRemoteAnimationRegistryTest - */ -@SmallTest -@Presubmit -@FlakyTest -@RunWith(AndroidJUnit4.class) -public class PendingRemoteAnimationRegistryTest extends ActivityTestsBase { - - @Mock RemoteAnimationAdapter mAdapter; - private PendingRemoteAnimationRegistry mRegistry; - private final OffsettableClock mClock = new OffsettableClock.Stopped(); - private TestHandler mHandler; - private ActivityManagerService mService; - - @Before - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - mService = createActivityManagerService(); - mService.mHandlerThread.getThreadHandler().runWithScissors(() -> { - mHandler = new TestHandler(null, mClock); - }, 0); - mRegistry = new PendingRemoteAnimationRegistry(mService, mHandler); - } - - @Test - public void testOverrideActivityOptions() { - mRegistry.addPendingAnimation("com.android.test", mAdapter); - ActivityOptions opts = ActivityOptions.makeBasic(); - opts = mRegistry.overrideOptionsIfNeeded("com.android.test", opts); - assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); - } - - @Test - public void testOverrideActivityOptions_null() { - mRegistry.addPendingAnimation("com.android.test", mAdapter); - final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null); - assertNotNull(opts); - assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); - } - - @Test - public void testTimeout() { - mRegistry.addPendingAnimation("com.android.test", mAdapter); - mClock.fastForward(5000); - mHandler.timeAdvance(); - assertNull(mRegistry.overrideOptionsIfNeeded("com.android.test", null)); - } - - @Test - public void testTimeout_overridenEntry() { - mRegistry.addPendingAnimation("com.android.test", mAdapter); - mClock.fastForward(2500); - mHandler.timeAdvance(); - mRegistry.addPendingAnimation("com.android.test", mAdapter); - mClock.fastForward(1000); - mHandler.timeAdvance(); - final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null); - assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java deleted file mode 100644 index 54f93a88a387..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.am; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.admin.IDeviceAdminService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.UserHandle; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.Pair; - -import org.mockito.ArgumentMatchers; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; - -@SmallTest -public class PersistentConnectionTest extends AndroidTestCase { - private static class MyConnection extends PersistentConnection { - public long uptimeMillis = 12345; - - public ArrayList> scheduledRunnables = new ArrayList<>(); - - public MyConnection(String tag, Context context, Handler handler, int userId, - ComponentName componentName, long rebindBackoffSeconds, - double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { - super(tag, context, handler, userId, componentName, - rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds); - } - - @Override - protected IDeviceAdminService asInterface(IBinder binder) { - return (IDeviceAdminService) binder; - } - - @Override - long injectUptimeMillis() { - return uptimeMillis; - } - - @Override - void injectPostAtTime(Runnable r, long uptimeMillis) { - scheduledRunnables.add(Pair.create(r, uptimeMillis)); - } - - @Override - void injectRemoveCallbacks(Runnable r) { - for (int i = scheduledRunnables.size() - 1; i >= 0; i--) { - if (scheduledRunnables.get(i).first.equals(r)) { - scheduledRunnables.remove(i); - } - } - } - - void elapse(long milliSeconds) { - uptimeMillis += milliSeconds; - - // Fire the scheduled runnables. - - // Note we collect first and then run all, because sometimes a scheduled runnable - // calls removeCallbacks. - final ArrayList list = new ArrayList<>(); - - for (int i = scheduledRunnables.size() - 1; i >= 0; i--) { - if (scheduledRunnables.get(i).second <= uptimeMillis) { - list.add(scheduledRunnables.get(i).first); - scheduledRunnables.remove(i); - } - } - - Collections.reverse(list); - for (Runnable r : list) { - r.run(); - } - } - } - - public void testAll() { - final Context context = mock(Context.class); - final int userId = 11; - final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); - final Handler handler = new Handler(Looper.getMainLooper()); - - final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, - /* rebindBackoffSeconds= */ 5, - /* rebindBackoffIncrease= */ 1.5, - /* rebindMaxBackoffSeconds= */ 11); - - assertFalse(conn.isBound()); - assertFalse(conn.isConnected()); - assertFalse(conn.isRebindScheduled()); - assertEquals(5000, conn.getNextBackoffMsForTest()); - assertNull(conn.getServiceBinder()); - - when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), - any(Handler.class), any(UserHandle.class))) - .thenReturn(true); - - // Call bind. - conn.bind(); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertFalse(conn.isRebindScheduled()); - assertNull(conn.getServiceBinder()); - - assertEquals(5000, conn.getNextBackoffMsForTest()); - - verify(context).bindServiceAsUser( - ArgumentMatchers.argThat(intent -> cn.equals(intent.getComponent())), - eq(conn.getServiceConnectionForTest()), - eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), - eq(handler), eq(UserHandle.of(userId))); - - // AM responds... - conn.getServiceConnectionForTest().onServiceConnected(cn, - new IDeviceAdminService.Stub() {}); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertTrue(conn.isConnected()); - assertNotNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - assertEquals(5000, conn.getNextBackoffMsForTest()); - - - // Now connected. - - // Call unbind... - conn.unbind(); - assertFalse(conn.isBound()); - assertFalse(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - // Caller bind again... - conn.bind(); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertFalse(conn.isRebindScheduled()); - assertNull(conn.getServiceBinder()); - - assertEquals(5000, conn.getNextBackoffMsForTest()); - - - // Now connected again. - - // The service got killed... - conn.getServiceConnectionForTest().onServiceDisconnected(cn); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - assertEquals(5000, conn.getNextBackoffMsForTest()); - - // Connected again... - conn.getServiceConnectionForTest().onServiceConnected(cn, - new IDeviceAdminService.Stub() {}); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertTrue(conn.isConnected()); - assertNotNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - assertEquals(5000, conn.getNextBackoffMsForTest()); - - - // Then the binding is "died"... - conn.getServiceConnectionForTest().onBindingDied(cn); - - assertFalse(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertTrue(conn.isRebindScheduled()); - - assertEquals(7500, conn.getNextBackoffMsForTest()); - - assertEquals( - Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), - conn.uptimeMillis + 5000)), - conn.scheduledRunnables); - - // 5000 ms later... - conn.elapse(5000); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - assertEquals(7500, conn.getNextBackoffMsForTest()); - - // Connected. - conn.getServiceConnectionForTest().onServiceConnected(cn, - new IDeviceAdminService.Stub() {}); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertTrue(conn.isConnected()); - assertNotNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - assertEquals(7500, conn.getNextBackoffMsForTest()); - - // Then the binding is "died"... - conn.getServiceConnectionForTest().onBindingDied(cn); - - assertFalse(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertTrue(conn.isRebindScheduled()); - - assertEquals(11000, conn.getNextBackoffMsForTest()); - - assertEquals( - Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), - conn.uptimeMillis + 7500)), - conn.scheduledRunnables); - - // Later... - conn.elapse(7500); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - assertEquals(11000, conn.getNextBackoffMsForTest()); - - - // Then the binding is "died"... - conn.getServiceConnectionForTest().onBindingDied(cn); - - assertFalse(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertTrue(conn.isRebindScheduled()); - - assertEquals(11000, conn.getNextBackoffMsForTest()); - - assertEquals( - Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), - conn.uptimeMillis + 11000)), - conn.scheduledRunnables); - - // Call unbind... - conn.unbind(); - assertFalse(conn.isBound()); - assertFalse(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertNull(conn.getServiceBinder()); - assertFalse(conn.isRebindScheduled()); - - // Call bind again... And now the backoff is reset to 5000. - conn.bind(); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isConnected()); - assertFalse(conn.isRebindScheduled()); - assertNull(conn.getServiceBinder()); - - assertEquals(5000, conn.getNextBackoffMsForTest()); - } - - public void testReconnectFiresAfterUnbind() { - final Context context = mock(Context.class); - final int userId = 11; - final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); - final Handler handler = new Handler(Looper.getMainLooper()); - - final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, - /* rebindBackoffSeconds= */ 5, - /* rebindBackoffIncrease= */ 1.5, - /* rebindMaxBackoffSeconds= */ 11); - - when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), - any(Handler.class), any(UserHandle.class))) - .thenReturn(true); - - // Bind. - conn.bind(); - - assertTrue(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertFalse(conn.isRebindScheduled()); - - conn.elapse(1000); - - // Service crashes. - conn.getServiceConnectionForTest().onBindingDied(cn); - - assertFalse(conn.isBound()); - assertTrue(conn.shouldBeBoundForTest()); - assertTrue(conn.isRebindScheduled()); - - assertEquals(7500, conn.getNextBackoffMsForTest()); - - // Call unbind. - conn.unbind(); - assertFalse(conn.isBound()); - assertFalse(conn.shouldBeBoundForTest()); - - // Now, at this point, it's possible that the scheduled runnable had already been fired - // before during the unbind() call, and waiting on mLock. - // To simulate it, we just call the runnable here. - conn.getBindForBackoffRunnableForTest().run(); - - // Should still not be bound. - assertFalse(conn.isBound()); - assertFalse(conn.shouldBeBoundForTest()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java deleted file mode 100644 index b73ac8934cb0..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ /dev/null @@ -1,1026 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; -import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.view.Display.DEFAULT_DISPLAY; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; - -import static java.lang.Integer.MAX_VALUE; - -import android.annotation.TestApi; -import android.app.ActivityManager; -import android.app.ActivityManager.RecentTaskInfo; -import android.app.ActivityManager.RunningTaskInfo; -import android.app.WindowConfiguration; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; -import android.content.pm.UserInfo; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Debug; -import android.os.Looper; -import android.os.RemoteException; -import android.os.SystemClock; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.MutableLong; -import android.util.SparseArray; -import android.util.SparseBooleanArray; - -import com.android.server.am.RecentTasks.Callbacks; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; - -/** - * atest FrameworksServicesTests:RecentTasksTest - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class RecentTasksTest extends ActivityTestsBase { - private static final int TEST_USER_0_ID = 0; - private static final int TEST_USER_1_ID = 10; - private static final int TEST_QUIET_USER_ID = 20; - private static final UserInfo DEFAULT_USER_INFO = new UserInfo(); - private static final UserInfo QUIET_USER_INFO = new UserInfo(); - private static int LAST_TASK_ID = 1; - private static int LAST_STACK_ID = 1; - private static int INVALID_STACK_ID = 999; - - private Context mContext = InstrumentationRegistry.getContext(); - private ActivityManagerService mService; - private ActivityDisplay mDisplay; - private ActivityDisplay mOtherDisplay; - private ActivityStack mStack; - private ActivityStack mHomeStack; - private TestTaskPersister mTaskPersister; - private TestRecentTasks mRecentTasks; - private TestRunningTasks mRunningTasks; - - private ArrayList mTasks; - private ArrayList mSameDocumentTasks; - - private CallbacksRecorder mCallbacksRecorder; - - class TestUserController extends UserController { - TestUserController(ActivityManagerService service) { - super(service); - } - - @Override - int[] getCurrentProfileIds() { - return new int[] { TEST_USER_0_ID, TEST_QUIET_USER_ID }; - } - - @Override - Set getProfileIds(int userId) { - Set profileIds = new HashSet<>(); - profileIds.add(TEST_USER_0_ID); - profileIds.add(TEST_QUIET_USER_ID); - return profileIds; - } - - @Override - UserInfo getUserInfo(int userId) { - switch (userId) { - case TEST_USER_0_ID: - case TEST_USER_1_ID: - return DEFAULT_USER_INFO; - case TEST_QUIET_USER_ID: - return QUIET_USER_INFO; - } - return null; - } - } - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); - mService = setupActivityManagerService(new MyTestActivityManagerService(mContext)); - mRecentTasks = (TestRecentTasks) mService.getRecentTasks(); - mRecentTasks.loadParametersFromResources(mContext.getResources()); - mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); - mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack); - mCallbacksRecorder = new CallbacksRecorder(); - mRecentTasks.registerCallback(mCallbacksRecorder); - QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE; - - mTasks = new ArrayList<>(); - mTasks.add(createTaskBuilder(".Task1").build()); - mTasks.add(createTaskBuilder(".Task2").build()); - mTasks.add(createTaskBuilder(".Task3").build()); - mTasks.add(createTaskBuilder(".Task4").build()); - mTasks.add(createTaskBuilder(".Task5").build()); - - mSameDocumentTasks = new ArrayList<>(); - mSameDocumentTasks.add(createDocumentTask(".DocumentTask1")); - mSameDocumentTasks.add(createDocumentTask(".DocumentTask1")); - } - - @Test - public void testCallbacks() throws Exception { - // Add some tasks - mRecentTasks.add(mTasks.get(0)); - mRecentTasks.add(mTasks.get(1)); - assertTrue(mCallbacksRecorder.added.contains(mTasks.get(0)) - && mCallbacksRecorder.added.contains(mTasks.get(1))); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - mCallbacksRecorder.clear(); - - // Remove some tasks - mRecentTasks.remove(mTasks.get(0)); - mRecentTasks.remove(mTasks.get(1)); - assertTrue(mCallbacksRecorder.added.isEmpty()); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(0))); - assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(1))); - mCallbacksRecorder.clear(); - - // Remove the callback, ensure we don't get any calls - mRecentTasks.unregisterCallback(mCallbacksRecorder); - mRecentTasks.add(mTasks.get(0)); - mRecentTasks.remove(mTasks.get(0)); - assertTrue(mCallbacksRecorder.added.isEmpty()); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - } - - @Test - public void testAddTasksNoMultiple_expectNoTrim() throws Exception { - // Add same non-multiple-task document tasks will remove the task (to re-add it) but not - // trim it - TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); - TaskRecord documentTask2 = createDocumentTask(".DocumentTask1"); - mRecentTasks.add(documentTask1); - mRecentTasks.add(documentTask2); - assertTrue(mCallbacksRecorder.added.contains(documentTask1)); - assertTrue(mCallbacksRecorder.added.contains(documentTask2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.contains(documentTask1)); - } - - @Test - public void testAddTasksMaxTaskRecents_expectNoTrim() throws Exception { - // Add a task hitting max-recents for that app will remove the task (to add the next one) - // but not trim it - TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); - TaskRecord documentTask2 = createDocumentTask(".DocumentTask1"); - documentTask1.maxRecents = 1; - documentTask2.maxRecents = 1; - mRecentTasks.add(documentTask1); - mRecentTasks.add(documentTask2); - assertTrue(mCallbacksRecorder.added.contains(documentTask1)); - assertTrue(mCallbacksRecorder.added.contains(documentTask2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.contains(documentTask1)); - } - - @Test - public void testAddTasksSameTask_expectNoTrim() throws Exception { - // Add a task that is already in the task list does not trigger any callbacks, it just - // moves in the list - TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); - mRecentTasks.add(documentTask1); - mRecentTasks.add(documentTask1); - assertTrue(mCallbacksRecorder.added.size() == 1); - assertTrue(mCallbacksRecorder.added.contains(documentTask1)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - } - - @Test - public void testAddTasksMultipleDocumentTasks_expectNoTrim() throws Exception { - // Add same multiple-task document tasks does not trim the first tasks - TaskRecord documentTask1 = createDocumentTask(".DocumentTask1", - FLAG_ACTIVITY_MULTIPLE_TASK); - TaskRecord documentTask2 = createDocumentTask(".DocumentTask1", - FLAG_ACTIVITY_MULTIPLE_TASK); - mRecentTasks.add(documentTask1); - mRecentTasks.add(documentTask2); - assertTrue(mCallbacksRecorder.added.size() == 2); - assertTrue(mCallbacksRecorder.added.contains(documentTask1)); - assertTrue(mCallbacksRecorder.added.contains(documentTask2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - } - - @Test - public void testAddTasksMultipleTasks_expectRemovedNoTrim() throws Exception { - // Add multiple same-affinity non-document tasks, ensure that it removes the other task, - // but that it does not trim it - TaskRecord task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) - .build(); - TaskRecord task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) - .build(); - mRecentTasks.add(task1); - assertTrue(mCallbacksRecorder.added.size() == 1); - assertTrue(mCallbacksRecorder.added.contains(task1)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - mCallbacksRecorder.clear(); - mRecentTasks.add(task2); - assertTrue(mCallbacksRecorder.added.size() == 1); - assertTrue(mCallbacksRecorder.added.contains(task2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.size() == 1); - assertTrue(mCallbacksRecorder.removed.contains(task1)); - } - - @Test - public void testAddTasksDifferentStacks_expectNoRemove() throws Exception { - // Adding the same task with different activity types should not trigger removal of the - // other task - TaskRecord task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) - .setStack(mHomeStack).build(); - TaskRecord task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) - .setStack(mStack).build(); - mRecentTasks.add(task1); - mRecentTasks.add(task2); - assertTrue(mCallbacksRecorder.added.size() == 2); - assertTrue(mCallbacksRecorder.added.contains(task1)); - assertTrue(mCallbacksRecorder.added.contains(task2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - } - - @Test - public void testAddTaskCompatibleActivityType_expectRemove() throws Exception { - // Test with undefined activity type since the type is not persisted by the task persister - // and we want to ensure that a new task will match a restored task - Configuration config1 = new Configuration(); - config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); - TaskRecord task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .build(); - task1.onConfigurationChanged(config1); - assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED); - mRecentTasks.add(task1); - mCallbacksRecorder.clear(); - - TaskRecord task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .build(); - assertTrue(task2.getActivityType() == ACTIVITY_TYPE_STANDARD); - mRecentTasks.add(task2); - assertTrue(mCallbacksRecorder.added.size() == 1); - assertTrue(mCallbacksRecorder.added.contains(task2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.size() == 1); - assertTrue(mCallbacksRecorder.removed.contains(task1)); - } - - @Test - public void testAddTaskCompatibleActivityTypeDifferentUser_expectNoRemove() throws Exception { - Configuration config1 = new Configuration(); - config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); - TaskRecord task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .setUserId(TEST_USER_0_ID) - .build(); - task1.onConfigurationChanged(config1); - assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED); - mRecentTasks.add(task1); - mCallbacksRecorder.clear(); - - TaskRecord task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .setUserId(TEST_USER_1_ID) - .build(); - assertTrue(task2.getActivityType() == ACTIVITY_TYPE_STANDARD); - mRecentTasks.add(task2); - assertTrue(mCallbacksRecorder.added.size() == 1); - assertTrue(mCallbacksRecorder.added.contains(task2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - } - - @Test - public void testAddTaskCompatibleWindowingMode_expectRemove() throws Exception { - Configuration config1 = new Configuration(); - config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED); - TaskRecord task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .build(); - task1.onConfigurationChanged(config1); - assertTrue(task1.getWindowingMode() == WINDOWING_MODE_UNDEFINED); - mRecentTasks.add(task1); - mCallbacksRecorder.clear(); - - Configuration config2 = new Configuration(); - config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - TaskRecord task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .build(); - task2.onConfigurationChanged(config2); - assertTrue(task2.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - mRecentTasks.add(task2); - - assertTrue(mCallbacksRecorder.added.size() == 1); - assertTrue(mCallbacksRecorder.added.contains(task2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.size() == 1); - assertTrue(mCallbacksRecorder.removed.contains(task1)); - } - - @Test - public void testAddTaskIncompatibleWindowingMode_expectNoRemove() throws Exception { - Configuration config1 = new Configuration(); - config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - TaskRecord task1 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .build(); - task1.onConfigurationChanged(config1); - assertTrue(task1.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - mRecentTasks.add(task1); - - Configuration config2 = new Configuration(); - config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); - TaskRecord task2 = createTaskBuilder(".Task1") - .setFlags(FLAG_ACTIVITY_NEW_TASK) - .setStack(mStack) - .build(); - task2.onConfigurationChanged(config2); - assertTrue(task2.getWindowingMode() == WINDOWING_MODE_PINNED); - mRecentTasks.add(task2); - - assertTrue(mCallbacksRecorder.added.size() == 2); - assertTrue(mCallbacksRecorder.added.contains(task1)); - assertTrue(mCallbacksRecorder.added.contains(task2)); - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); - } - - @Test - public void testUsersTasks() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - - // Setup some tasks for the users - mTaskPersister.userTaskIdsOverride = new SparseBooleanArray(); - mTaskPersister.userTaskIdsOverride.put(1, true); - mTaskPersister.userTaskIdsOverride.put(2, true); - mTaskPersister.userTasksOverride = new ArrayList<>(); - mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build()); - mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build()); - - // Assert no user tasks are initially loaded - assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0); - - // Load user 0 tasks - mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); - assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); - assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); - assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); - - // Load user 1 tasks - mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID); - assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); - assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID)); - assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); - assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); - assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_1_ID)); - assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_1_ID)); - - // Unload user 1 tasks - mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_1_ID); - assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); - assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID)); - assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); - assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); - - // Unload user 0 tasks - mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID); - assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); - assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID)); - } - - @Test - public void testOrderedIteration() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - TaskRecord task1 = createTaskBuilder(".Task1").build(); - task1.lastActiveTime = new Random().nextInt(); - TaskRecord task2 = createTaskBuilder(".Task1").build(); - task2.lastActiveTime = new Random().nextInt(); - TaskRecord task3 = createTaskBuilder(".Task1").build(); - task3.lastActiveTime = new Random().nextInt(); - TaskRecord task4 = createTaskBuilder(".Task1").build(); - task4.lastActiveTime = new Random().nextInt(); - mRecentTasks.add(task1); - mRecentTasks.add(task2); - mRecentTasks.add(task3); - mRecentTasks.add(task4); - - MutableLong prevLastActiveTime = new MutableLong(0); - final ArrayList tasks = mRecentTasks.getRawTasks(); - for (int i = 0; i < tasks.size(); i++) { - final TaskRecord task = tasks.get(i); - assertTrue(task.lastActiveTime >= prevLastActiveTime.value); - prevLastActiveTime.value = task.lastActiveTime; - } - } - - @Test - public void testTrimToGlobalMaxNumRecents() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - - // Limit the global maximum number of recent tasks to a fixed size - mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */); - - // Add N+1 tasks - mRecentTasks.add(mTasks.get(0)); - mRecentTasks.add(mTasks.get(1)); - mRecentTasks.add(mTasks.get(2)); - - // Ensure that the last task was trimmed as an inactive task - assertTrimmed(mTasks.get(0)); - } - - @Test - public void testTrimQuietProfileTasks() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build(); - TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build(); - mRecentTasks.add(qt1); - mRecentTasks.add(qt2); - - mRecentTasks.add(mTasks.get(0)); - mRecentTasks.add(mTasks.get(1)); - - // Ensure that the quiet user's tasks was trimmed once the new tasks were added - assertTrimmed(qt1, qt2); - } - - @Test - public void testSessionDuration() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */); - - TaskRecord t1 = createTaskBuilder(".Task1").build(); - t1.touchActiveTime(); - mRecentTasks.add(t1); - - // Force a small sleep just beyond the session duration - SystemClock.sleep(75); - - TaskRecord t2 = createTaskBuilder(".Task2").build(); - t2.touchActiveTime(); - mRecentTasks.add(t2); - - // Assert that the old task has been removed due to being out of the active session - assertTrimmed(t1); - } - - @Test - public void testVisibleTasks_excludedFromRecents() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); - - TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1") - .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .build(); - TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2") - .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .build(); - - mRecentTasks.add(excludedTask1); - mRecentTasks.add(mTasks.get(0)); - mRecentTasks.add(mTasks.get(1)); - mRecentTasks.add(mTasks.get(2)); - mRecentTasks.add(excludedTask2); - - // The last excluded task should be trimmed, while the first-most excluded task should not - assertTrimmed(excludedTask1); - } - - @Test - public void testVisibleTasks_minNum() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */); - - for (int i = 0; i < 4; i++) { - final TaskRecord task = mTasks.get(i); - task.touchActiveTime(); - mRecentTasks.add(task); - } - - // Force a small sleep just beyond the session duration - SystemClock.sleep(50); - - // Add a new task to trigger tasks to be trimmed - mRecentTasks.add(mTasks.get(4)); - - // Ensure that there are a minimum number of tasks regardless of session length - assertNoTasksTrimmed(); - } - - @Test - public void testVisibleTasks_maxNum() throws Exception { - mRecentTasks.setOnlyTestVisibleRange(); - mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */); - - for (int i = 0; i < 5; i++) { - final TaskRecord task = mTasks.get(i); - task.touchActiveTime(); - mRecentTasks.add(task); - } - - // Ensure that only the last number of max tasks are kept - assertTrimmed(mTasks.get(0), mTasks.get(1)); - } - - @Test - public void testBackStackTasks_expectNoTrim() throws Exception { - mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); - - final MyTestActivityStackSupervisor supervisor = - (MyTestActivityStackSupervisor) mService.mStackSupervisor; - final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); - final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); - supervisor.setHomeStack(homeStack); - - // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all - // the tasks belong in stacks above the home stack - mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build()); - - assertNoTasksTrimmed(); - } - - @Test - public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception { - mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); - - final MyTestActivityStackSupervisor supervisor = - (MyTestActivityStackSupervisor) mService.mStackSupervisor; - final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor); - final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); - final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); - supervisor.setHomeStack(homeStack); - - // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind - // the home stack is trimmed once a new task is added - final TaskRecord behindHomeTask = createTaskBuilder(".Task1") - .setStack(behindHomeStack) - .build(); - mRecentTasks.add(behindHomeTask); - mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build()); - - assertTrimmed(behindHomeTask); - } - - @Test - public void testOtherDisplayTasks_expectNoTrim() throws Exception { - mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); - - final MyTestActivityStackSupervisor supervisor = - (MyTestActivityStackSupervisor) mService.mStackSupervisor; - final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); - final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor); - supervisor.setHomeStack(homeStack); - - // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not - // removed - mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); - mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build()); - mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build()); - mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build()); - - assertNoTasksTrimmed(); - } - - @Test - public void testRemovePackageByName() throws Exception { - // Add a number of tasks with the same package name - mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".Task1").build()); - mRecentTasks.add(createTaskBuilder("com.android.pkg2", ".Task2").build()); - mRecentTasks.add(createTaskBuilder("com.android.pkg3", ".Task3").build()); - mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".Task4").build()); - mRecentTasks.removeTasksByPackageName("com.android.pkg1", TEST_USER_0_ID); - - final ArrayList tasks = mRecentTasks.getRawTasks(); - for (int i = 0; i < tasks.size(); i++) { - if (tasks.get(i).intent.getComponent().getPackageName().equals("com.android.pkg1")) { - fail("Expected com.android.pkg1 tasks to be removed"); - } - } - } - - @Test - public void testNotRecentsComponent_denyApiAccess() throws Exception { - doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(), - anyInt(), anyInt()); - - // Expect the following methods to fail due to recents component not being set - mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION); - testRecentTasksApis(false /* expectNoSecurityException */); - // Don't throw for the following tests - mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY); - testGetTasksApis(false /* expectNoSecurityException */); - } - - @Test - public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception { - doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(), - anyInt(), anyInt()); - - // Set the recents component and ensure that the following calls do not fail - mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT); - testRecentTasksApis(true /* expectNoSecurityException */); - testGetTasksApis(true /* expectNoSecurityException */); - } - - private void testRecentTasksApis(boolean expectCallable) { - assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID)); - assertSecurityException(expectCallable, - () -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED})); - assertSecurityException(expectCallable, - () -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED})); - assertSecurityException(expectCallable, () -> mService.removeTask(0)); - assertSecurityException(expectCallable, - () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true)); - assertSecurityException(expectCallable, - () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true)); - assertSecurityException(expectCallable, - () -> mService.setTaskWindowingModeSplitScreenPrimary(0, - SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true)); - assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true)); - assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0)); - assertSecurityException(expectCallable, - () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect())); - assertSecurityException(expectCallable, - () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0)); - assertSecurityException(expectCallable, - () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(), - new Rect())); - assertSecurityException(expectCallable, - () -> mService.resizePinnedStack(new Rect(), new Rect())); - assertSecurityException(expectCallable, () -> mService.getAllStackInfos()); - assertSecurityException(expectCallable, - () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED)); - assertSecurityException(expectCallable, () -> { - try { - mService.getFocusedStackInfo(); - } catch (RemoteException e) { - // Ignore - } - }); - assertSecurityException(expectCallable, - () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); - assertSecurityException(expectCallable, - () -> mService.startActivityFromRecents(0, new Bundle())); - assertSecurityException(expectCallable, - () -> mService.getTaskSnapshot(0, true)); - assertSecurityException(expectCallable, () -> { - try { - mService.registerTaskStackListener(null); - } catch (RemoteException e) { - // Ignore - } - }); - assertSecurityException(expectCallable, () -> { - try { - mService.unregisterTaskStackListener(null); - } catch (RemoteException e) { - // Ignore - } - }); - assertSecurityException(expectCallable, () -> mService.getTaskDescription(0)); - assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0)); - assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, - null)); - assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true)); - assertSecurityException(expectCallable, () -> mService.stopAppSwitches()); - assertSecurityException(expectCallable, () -> mService.resumeAppSwitches()); - } - - private void testGetTasksApis(boolean expectCallable) { - mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID); - mService.getTasks(MAX_VALUE); - if (expectCallable) { - assertTrue(mRecentTasks.lastAllowed); - assertTrue(mRunningTasks.lastAllowed); - } else { - assertFalse(mRecentTasks.lastAllowed); - assertFalse(mRunningTasks.lastAllowed); - } - } - - private TaskBuilder createTaskBuilder(String className) { - return createTaskBuilder(mContext.getPackageName(), className); - } - - private TaskBuilder createTaskBuilder(String packageName, String className) { - return new TaskBuilder(mService.mStackSupervisor) - .setComponent(new ComponentName(packageName, className)) - .setStack(mStack) - .setTaskId(LAST_TASK_ID++) - .setUserId(TEST_USER_0_ID); - } - - private TaskRecord createDocumentTask(String className) { - return createDocumentTask(className, 0); - } - - private TaskRecord createDocumentTask(String className, int flags) { - TaskRecord task = createTaskBuilder(className) - .setFlags(FLAG_ACTIVITY_NEW_DOCUMENT | flags) - .build(); - task.affinity = null; - task.maxRecents = ActivityManager.getMaxAppRecentsLimitStatic(); - return task; - } - - private boolean arrayContainsUser(int[] userIds, int targetUserId) { - Arrays.sort(userIds); - return Arrays.binarySearch(userIds, targetUserId) >= 0; - } - - private void assertNoTasksTrimmed() { - assertTrimmed(); - } - - private void assertTrimmed(TaskRecord... tasks) { - final ArrayList trimmed = mCallbacksRecorder.trimmed; - final ArrayList removed = mCallbacksRecorder.removed; - assertTrue("Expected " + tasks.length + " trimmed tasks, got " + trimmed.size(), - trimmed.size() == tasks.length); - assertTrue("Expected " + tasks.length + " removed tasks, got " + removed.size(), - removed.size() == tasks.length); - for (TaskRecord task : tasks) { - assertTrue("Expected trimmed task: " + task, trimmed.contains(task)); - assertTrue("Expected removed task: " + task, removed.contains(task)); - } - } - - private void assertSecurityException(boolean expectCallable, Runnable runnable) { - boolean noSecurityException = true; - try { - runnable.run(); - } catch (SecurityException se) { - noSecurityException = false; - } catch (Exception e) { - // We only care about SecurityExceptions, fall through here - e.printStackTrace(); - } - if (noSecurityException != expectCallable) { - fail("Expected callable: " + expectCallable + " but got no security exception: " - + noSecurityException); - } - } - - private class MyTestActivityManagerService extends TestActivityManagerService { - MyTestActivityManagerService(Context context) { - super(context); - } - - @Override - protected ActivityStackSupervisor createTestSupervisor() { - return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper()); - } - - @Override - protected RecentTasks createRecentTasks() { - return new TestRecentTasks(this, mTaskPersister, new TestUserController(this)); - } - - @Override - public boolean isUserRunning(int userId, int flags) { - return true; - } - } - - private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor { - public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) { - super(service, looper); - } - - @Override - public void initialize() { - super.initialize(); - mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); - mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); - attachDisplay(mOtherDisplay); - attachDisplay(mDisplay); - } - - @Override - RunningTasks createRunningTasks() { - mRunningTasks = new TestRunningTasks(); - return mRunningTasks; - } - - void setHomeStack(ActivityStack stack) { - mHomeStack = stack; - } - } - - private class MyTestActivityStack extends TestActivityStack { - private ActivityDisplay mDisplay = null; - - MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) { - super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true); - mDisplay = display; - } - - @Override - ActivityDisplay getDisplay() { - if (mDisplay != null) { - return mDisplay; - } - return super.getDisplay(); - } - } - - private static class CallbacksRecorder implements Callbacks { - ArrayList added = new ArrayList<>(); - ArrayList trimmed = new ArrayList<>(); - ArrayList removed = new ArrayList<>(); - - void clear() { - added.clear(); - trimmed.clear(); - removed.clear(); - } - - @Override - public void onRecentTaskAdded(TaskRecord task) { - added.add(task); - } - - @Override - public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { - if (wasTrimmed) { - trimmed.add(task); - } - removed.add(task); - } - } - - private static class TestTaskPersister extends TaskPersister { - SparseBooleanArray userTaskIdsOverride; - ArrayList userTasksOverride; - - TestTaskPersister(File workingDir) { - super(workingDir); - } - - @Override - SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { - if (userTaskIdsOverride != null) { - return userTaskIdsOverride; - } - return super.loadPersistedTaskIdsForUser(userId); - } - - @Override - List restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) { - if (userTasksOverride != null) { - return userTasksOverride; - } - return super.restoreTasksForUserLocked(userId, preaddedTasks); - } - } - - private static class TestRecentTasks extends RecentTasks { - static final int GRANT = 0; - static final int DENY = 1; - static final int DENY_THROW_SECURITY_EXCEPTION = 2; - - private boolean mOverrideIsCallerRecents; - private boolean mIsTrimmableOverride; - private int mIsCallerRecentsPolicy; - - boolean lastAllowed; - - TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister, - UserController userController) { - super(service, taskPersister, userController); - } - - @Override - boolean isCallerRecents(int callingUid) { - if (mOverrideIsCallerRecents) { - switch (mIsCallerRecentsPolicy) { - case GRANT: - return true; - case DENY: - return false; - case DENY_THROW_SECURITY_EXCEPTION: - throw new SecurityException(); - } - } - return super.isCallerRecents(callingUid); - } - - void setIsCallerRecentsOverride(int policy) { - mOverrideIsCallerRecents = true; - mIsCallerRecentsPolicy = policy; - } - - /** - * To simplify the setup for some tests, the caller can request that we only rely on the - * visible range test to determine what is trimmable. In this case, we don't try to - * use the stack order to determine additionally if the task is trimmable when it is not - * in the visible range. - */ - void setOnlyTestVisibleRange() { - mIsTrimmableOverride = true; - } - - @Override - ParceledListSlice getRecentTasks(int maxNum, int flags, - boolean getTasksAllowed, - boolean getDetailedTasks, int userId, int callingUid) { - lastAllowed = getTasksAllowed; - return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId, - callingUid); - } - - @Override - protected boolean isTrimmable(TaskRecord task) { - return mIsTrimmableOverride || super.isTrimmable(task); - } - } - - private static class TestRunningTasks extends RunningTasks { - boolean lastAllowed; - - @Override - void getTasks(int maxNum, List list, int ignoreActivityType, - int ignoreWindowingMode, SparseArray activityDisplays, - int callingUid, boolean allowed) { - lastAllowed = allowed; - super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays, - callingUid, allowed); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java deleted file mode 100644 index eefd973112f5..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.IRecentsAnimationRunner; -import com.android.server.AttributeCache; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * atest FrameworksServicesTests:RecentsAnimationTest - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class RecentsAnimationTest extends ActivityTestsBase { - private static final int TEST_CALLING_PID = 3; - - private Context mContext = InstrumentationRegistry.getContext(); - private ActivityManagerService mService; - private ComponentName mRecentsComponent; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity"); - mService = setupActivityManagerService(new MyTestActivityManagerService(mContext)); - AttributeCache.init(mContext); - } - - @Test - public void testCancelAnimationOnStackOrderChange() throws Exception { - ActivityStack fullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - ActivityStack recentsStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); - ActivityRecord recentsActivity = new ActivityBuilder(mService) - .setComponent(mRecentsComponent) - .setCreateTask(true) - .setStack(recentsStack) - .build(); - ActivityStack fullscreenStack2 = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - ActivityRecord fsActivity = new ActivityBuilder(mService) - .setComponent(new ComponentName(mContext.getPackageName(), "App1")) - .setCreateTask(true) - .setStack(fullscreenStack2) - .build(); - doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation(); - - // Start the recents animation - Intent recentsIntent = new Intent(); - recentsIntent.setComponent(mRecentsComponent); - mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class)); - - fullscreenStack.moveToFront("Activity start"); - - // Ensure that the recents animation was canceled - verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously( - eq(REORDER_KEEP_IN_PLACE), any()); - } - - private class MyTestActivityManagerService extends TestActivityManagerService { - MyTestActivityManagerService(Context context) { - super(context); - } - - @Override - protected RecentTasks createRecentTasks() { - RecentTasks recents = mock(RecentTasks.class); - doReturn(mRecentsComponent).when(recents).getRecentsComponent(); - System.out.println(mRecentsComponent); - return recents; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java deleted file mode 100644 index c6ce7e1188e8..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM; - -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManager.RunningTaskInfo; -import android.content.ComponentName; -import android.content.Context; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.SparseArray; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; - -/** - * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class RunningTasksTest extends ActivityTestsBase { - - private Context mContext = InstrumentationRegistry.getContext(); - private ActivityManagerService mService; - - private RunningTasks mRunningTasks; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mService = createActivityManagerService(); - mRunningTasks = new RunningTasks(); - } - - @Test - public void testCollectTasksByLastActiveTime() throws Exception { - // Create a number of stacks with tasks (of incrementing active time) - final ActivityStackSupervisor supervisor = mService.mStackSupervisor; - final SparseArray displays = new SparseArray<>(); - final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY); - displays.put(DEFAULT_DISPLAY, display); - - final int numStacks = 2; - for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) { - final ActivityStack stack = new TestActivityStack(display, stackIndex, supervisor, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true); - display.addChild(stack, POSITION_BOTTOM); - } - - final int numTasks = 10; - int activeTime = 0; - for (int i = 0; i < numTasks; i++) { - createTask(display.getChildAt(i % numStacks), ".Task" + i, i, activeTime++); - } - - // Ensure that the latest tasks were returned in order of decreasing last active time, - // collected from all tasks across all the stacks - final int numFetchTasks = 5; - ArrayList tasks = new ArrayList<>(); - mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED, - displays, -1 /* callingUid */, true /* allowed */); - assertTrue(tasks.size() == numFetchTasks); - for (int i = 0; i < numFetchTasks; i++) { - assertTrue(tasks.get(i).id == (numTasks - i - 1)); - } - - // Ensure that requesting more than the total number of tasks only returns the subset - // and does not crash - tasks.clear(); - mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED, - displays, -1 /* callingUid */, true /* allowed */); - assertTrue(tasks.size() == numTasks); - for (int i = 0; i < numTasks; i++) { - assertTrue(tasks.get(i).id == (numTasks - i - 1)); - } - } - - /** - * Create a task with a single activity in it, with the given last active time. - */ - private TaskRecord createTask(ActivityStack stack, String className, int taskId, - int lastActiveTime) { - final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) - .setComponent(new ComponentName(mContext.getPackageName(), className)) - .setTaskId(taskId) - .setStack(stack) - .build(); - task.lastActiveTime = lastActiveTime; - final ActivityRecord activity = new ActivityBuilder(mService) - .setTask(task) - .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity")) - .build(); - return task; - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java b/services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java deleted file mode 100644 index 168bc1782d56..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/SafeActivityOptionsTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static org.junit.Assert.assertEquals; - -import android.app.ActivityOptions; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@MediumTest -@Presubmit -@FlakyTest -@RunWith(AndroidJUnit4.class) -public class SafeActivityOptionsTest { - - @Test - public void testMerge() { - final ActivityOptions opts1 = ActivityOptions.makeBasic(); - opts1.setLaunchDisplayId(5); - final ActivityOptions opts2 = ActivityOptions.makeBasic(); - opts2.setLaunchDisplayId(6); - final SafeActivityOptions options = new SafeActivityOptions(opts1); - final ActivityOptions result = options.mergeActivityOptions(opts1, opts2); - assertEquals(6, result.getLaunchDisplayId()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java deleted file mode 100644 index 3d323f0eb783..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import android.content.pm.ActivityInfo.WindowLayout; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import android.view.Gravity; - -import org.junit.runner.RunWith; -import org.junit.Before; -import org.junit.Test; - -import org.mockito.invocation.InvocationOnMock; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - -import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.doAnswer; -import static org.junit.Assert.assertEquals; - - -/** - * Tests for exercising resizing task bounds. - * - * Build/Install/Run: - * atest FrameworksServicesTests:TaskLaunchParamsModifierTests - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskLaunchParamsModifierTests extends ActivityTestsBase { - private final static int STACK_WIDTH = 100; - private final static int STACK_HEIGHT = 200; - - private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT); - - private ActivityManagerService mService; - private ActivityStack mStack; - private TaskRecord mTask; - - private TaskLaunchParamsModifier mPositioner; - - private LaunchParamsController.LaunchParams mCurrent; - private LaunchParamsController.LaunchParams mResult; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mService = createActivityManagerService(); - mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mStack.requestResize(STACK_BOUNDS); - - // We must create the task after resizing to make sure it does not inherit the stack - // dimensions on resize. - mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); - - mPositioner = new TaskLaunchParamsModifier(); - - mResult = new LaunchParamsController.LaunchParams(); - mCurrent = new LaunchParamsController.LaunchParams(); - } - - /** - * Ensures that the setup bounds are set as expected with the stack bounds set and the task - * bounds still {@code null}. - * @throws Exception - */ - @Test - public void testInitialBounds() throws Exception { - assertEquals(mStack.getOverrideBounds(), STACK_BOUNDS); - assertEquals(mTask.getOverrideBounds(), new Rect()); - } - - /** - * Ensures that a task positioned with no {@link WindowLayout} receives the default launch - * position. - * @throws Exception - */ - @Test - public void testLaunchNoWindowLayout() throws Exception { - assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, null /*layout*/, - null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult)); - assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult.mBounds); - } - - /** - * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch - * position. - * @throws Exception - */ - @Test - public void testlaunchEmptyWindowLayout() throws Exception { - assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, - new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/, - null /*source*/, null /*options*/, mCurrent, mResult)); - assertEquals(mResult.mBounds, getDefaultBounds(Gravity.NO_GRAVITY)); - } - - /** - * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned - * according to specification. - * @throws Exception - */ - @Test - public void testlaunchWindowLayoutGravity() throws Exception { - // Unspecified gravity should be ignored - testGravity(Gravity.NO_GRAVITY); - - // Unsupported gravity should be ignored - testGravity(Gravity.LEFT); - testGravity(Gravity.RIGHT); - - // Test defaults for vertical gravities - testGravity(Gravity.TOP); - testGravity(Gravity.BOTTOM); - - // Test corners - testGravity(Gravity.TOP | Gravity.LEFT); - testGravity(Gravity.TOP | Gravity.RIGHT); - testGravity(Gravity.BOTTOM | Gravity.LEFT); - testGravity(Gravity.BOTTOM | Gravity.RIGHT); - } - - private void testGravity(int gravity) { - try { - assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, - new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/, - null /*source*/, null /*options*/, mCurrent, mResult)); - assertEquals(mResult.mBounds, getDefaultBounds(gravity)); - } finally { - mCurrent.reset(); - mResult.reset(); - } - } - - /** - * Ensures that a task which causes a conflict with another task when positioned is adjusted as - * expected. - * @throws Exception - */ - @Test - public void testLaunchWindowCenterConflict() throws Exception { - testConflict(Gravity.NO_GRAVITY); - testConflict(Gravity.TOP); - testConflict(Gravity.BOTTOM); - testConflict(Gravity.TOP | Gravity.LEFT); - testConflict(Gravity.TOP | Gravity.RIGHT); - testConflict(Gravity.BOTTOM | Gravity.LEFT); - testConflict(Gravity.BOTTOM | Gravity.RIGHT); - } - - private void testConflict(int gravity) { - final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0); - - // layout first task - mService.mStackSupervisor.getLaunchParamsController().layoutTask(mTask, layout); - - // Second task will be laid out on top of the first so starting bounds is the same. - final Rect expectedBounds = new Rect(mTask.getOverrideBounds()); - - ActivityRecord activity = null; - TaskRecord secondTask = null; - - // wrap with try/finally to ensure cleanup of activity/stack. - try { - // empty tasks are ignored in conflicts - activity = new ActivityBuilder(mService).setTask(mTask).build(); - - // Create secondary task - secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); - - // layout second task - assertEquals(RESULT_CONTINUE, - mPositioner.onCalculate(secondTask, layout, null /*activity*/, - null /*source*/, null /*options*/, mCurrent, mResult)); - - if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT) - || (gravity & (Gravity.BOTTOM | Gravity.RIGHT)) - == (Gravity.BOTTOM | Gravity.RIGHT)) { - expectedBounds.offset(-TaskLaunchParamsModifier.getHorizontalStep( - mStack.getOverrideBounds()), 0); - } else if ((gravity & Gravity.TOP) == Gravity.TOP - || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { - expectedBounds.offset( - TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), 0); - } else { - expectedBounds.offset( - TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), - TaskLaunchParamsModifier.getVerticalStep(mStack.getOverrideBounds())); - } - - assertEquals(mResult.mBounds, expectedBounds); - } finally { - // Remove task and activity to prevent influencing future tests - if (activity != null) { - mTask.removeActivity(activity); - } - - if (secondTask != null) { - mStack.removeTask(secondTask, "cleanup", ActivityStack.REMOVE_TASK_MODE_DESTROYING); - } - } - } - - private Rect getDefaultBounds(int gravity) { - final Rect bounds = new Rect(); - bounds.set(mStack.getOverrideBounds()); - - final int verticalInset = - TaskLaunchParamsModifier.getFreeformStartTop(mStack.getOverrideBounds()); - final int horizontalInset = - TaskLaunchParamsModifier.getFreeformStartLeft(mStack.getOverrideBounds()); - - bounds.inset(horizontalInset, verticalInset); - - if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)) { - bounds.offsetTo(horizontalInset * 2, 0); - } else if ((gravity & Gravity.TOP) == Gravity.TOP) { - bounds.offsetTo(0, 0); - } else if ((gravity & (Gravity.BOTTOM | Gravity.RIGHT)) - == (Gravity.BOTTOM | Gravity.RIGHT)) { - bounds.offsetTo(horizontalInset * 2, verticalInset * 2); - } else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { - bounds.offsetTo(0, verticalInset * 2); - } - - return bounds; - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java deleted file mode 100644 index 9e6055d55e0f..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import android.content.pm.UserInfo; -import android.os.Environment; -import android.os.UserHandle; -import android.os.UserManager; -import android.test.AndroidTestCase; -import android.util.Log; -import android.util.SparseBooleanArray; - -import com.android.server.am.TaskPersister; - -import java.io.File; -import java.util.Random; - -/** - * atest FrameworksServicesTests:TaskPersisterTest - */ -public class TaskPersisterTest extends AndroidTestCase { - private static final String TEST_USER_NAME = "AM-Test-User"; - - private TaskPersister mTaskPersister; - private int testUserId; - private UserManager mUserManager; - - @Override - public void setUp() throws Exception { - super.setUp(); - mUserManager = UserManager.get(getContext()); - mTaskPersister = new TaskPersister(getContext().getFilesDir()); - testUserId = createUser(TEST_USER_NAME, 0); - } - - @Override - public void tearDown() throws Exception { - super.tearDown(); - mTaskPersister.unloadUserDataFromMemory(testUserId); - removeUser(testUserId); - } - - private int getRandomTaskIdForUser(int userId) { - int taskId = (int) (Math.random() * UserHandle.PER_USER_RANGE); - taskId += UserHandle.PER_USER_RANGE * userId; - return taskId; - } - - public void testTaskIdsPersistence() { - SparseBooleanArray taskIdsOnFile = new SparseBooleanArray(); - for (int i = 0; i < 100; i++) { - taskIdsOnFile.put(getRandomTaskIdForUser(testUserId), true); - } - mTaskPersister.writePersistedTaskIdsForUser(taskIdsOnFile, testUserId); - SparseBooleanArray newTaskIdsOnFile = mTaskPersister - .loadPersistedTaskIdsForUser(testUserId); - assertTrue("TaskIds written differ from TaskIds read back from file", - taskIdsOnFile.equals(newTaskIdsOnFile)); - } - - private int createUser(String name, int flags) { - UserInfo user = mUserManager.createUser(name, flags); - if (user == null) { - fail("Error while creating the test user: " + TEST_USER_NAME); - } - return user.id; - } - - private void removeUser(int userId) { - if (!mUserManager.removeUser(userId)) { - fail("Error while removing the test user: " + TEST_USER_NAME); - } - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java b/services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java deleted file mode 100644 index 057fdc8c10bb..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/TaskRecordTests.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.server.am; - -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.res.XmlResourceParser; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.service.voice.IVoiceInteractionSession; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Xml; - -import com.android.frameworks.servicestests.R; -import com.android.internal.app.IVoiceInteractor; -import com.android.server.am.TaskRecord.TaskRecordFactory; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStream; -import java.io.Reader; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Comparator; - -/** - * Tests for exercising {@link TaskRecord}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.am.TaskRecordTests - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskRecordTests extends ActivityTestsBase { - - private static final String TASK_TAG = "task"; - - private ActivityManagerService mService; - - @Before - public void setUp() throws Exception { - super.setUp(); - TaskRecord.setTaskRecordFactory(null); - mService = createActivityManagerService(); - } - - @Test - public void testRestoreWindowedTask() throws Exception { - final TaskRecord expected = createTaskRecord(64); - expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100); - - final File serializedFile = serializeToFile(expected); - - try { - final TaskRecord actual = restoreFromFile(serializedFile); - assertEquals(expected.taskId, actual.taskId); - assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds); - } finally { - serializedFile.delete(); - } - } - - @Test - public void testDefaultTaskFactoryNotNull() throws Exception { - assertNotNull(TaskRecord.getTaskRecordFactory()); - } - - @Test - public void testCreateTestRecordUsingCustomizedFactory() throws Exception { - TestTaskRecordFactory factory = new TestTaskRecordFactory(); - TaskRecord.setTaskRecordFactory(factory); - - assertFalse(factory.mCreated); - - TaskRecord.create(null, 0, null, null, null, null); - - assertTrue(factory.mCreated); - } - - @Test - public void testReturnsToHomeStack() throws Exception { - final TaskRecord task = createTaskRecord(1); - assertFalse(task.returnsToHomeStack()); - task.intent = null; - assertFalse(task.returnsToHomeStack()); - task.intent = new Intent(); - assertFalse(task.returnsToHomeStack()); - task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); - assertTrue(task.returnsToHomeStack()); - } - - private File serializeToFile(TaskRecord r) throws IOException, XmlPullParserException { - final File tmpFile = File.createTempFile(r.taskId + "_task_", "xml"); - - try (final OutputStream os = new FileOutputStream(tmpFile)) { - final XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(os, "UTF-8"); - serializer.startDocument(null, true); - serializer.startTag(null, TASK_TAG); - r.saveToXml(serializer); - serializer.endTag(null, TASK_TAG); - serializer.endDocument(); - } - - return tmpFile; - } - - private TaskRecord restoreFromFile(File file) throws IOException, XmlPullParserException { - try (final Reader reader = new BufferedReader(new FileReader(file))) { - final XmlPullParser parser = Xml.newPullParser(); - parser.setInput(reader); - assertEquals(XmlPullParser.START_TAG, parser.next()); - assertEquals(TASK_TAG, parser.getName()); - return TaskRecord.restoreFromXml(parser, mService.mStackSupervisor); - } - } - - private TaskRecord createTaskRecord(int taskId) { - return new TaskRecord(mService, taskId, new Intent(), null, null, null, null, null, false, - false, false, 0, 10050, null, new ArrayList<>(), 0, false, null, 0, 0, 0, 0, 0, - null, 0, false, false, false, 0, 0); - } - - private static class TestTaskRecordFactory extends TaskRecordFactory { - private boolean mCreated = false; - - @Override - TaskRecord create(ActivityManagerService service, int taskId, ActivityInfo info, - Intent intent, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { - mCreated = true; - return null; - } - - @Override - TaskRecord create(ActivityManagerService service, int taskId, ActivityInfo info, - Intent intent, - ActivityManager.TaskDescription taskDescription) { - mCreated = true; - return null; - } - - @Override - TaskRecord create(ActivityManagerService service, int taskId, Intent intent, - Intent affinityIntent, String affinity, String rootAffinity, - ComponentName realActivity, - ComponentName origActivity, boolean rootWasReset, boolean autoRemoveRecents, - boolean askedCompatMode, int userId, int effectiveUid, String lastDescription, - ArrayList activities, long lastTimeMoved, - boolean neverRelinquishIdentity, - ActivityManager.TaskDescription lastTaskDescription, - int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, - int callingUid, String callingPackage, int resizeMode, - boolean supportsPictureInPicture, - boolean realActivitySuspended, boolean userSetupComplete, int minWidth, - int minHeight) { - mCreated = true; - return null; - } - - @Override - TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor) - throws IOException, XmlPullParserException { - mCreated = true; - return null; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java b/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java deleted file mode 100644 index 0359096892bb..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/TaskStackChangedListenerTest.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import static android.support.test.InstrumentationRegistry.getInstrumentation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.Activity; -import android.app.ActivityManager; -import android.app.ActivityManager.TaskDescription; -import android.app.IActivityManager; -import android.app.ITaskStackListener; -import android.app.Instrumentation.ActivityMonitor; -import android.app.TaskStackListener; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.res.Resources.Theme; -import android.os.RemoteException; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.support.test.uiautomator.UiDevice; -import android.text.TextUtils; -import android.util.Pair; -import com.android.internal.annotations.GuardedBy; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class TaskStackChangedListenerTest { - - private IActivityManager mService; - private ITaskStackListener mTaskStackListener; - - private static final Object sLock = new Object(); - @GuardedBy("sLock") - private static boolean sTaskStackChangedCalled; - private static boolean sActivityBResumed; - - @Before - public void setUp() throws Exception { - mService = ActivityManager.getService(); - } - - @After - public void tearDown() throws Exception { - mService.unregisterTaskStackListener(mTaskStackListener); - mTaskStackListener = null; - } - - @Test - public void testTaskStackChanged_afterFinish() throws Exception { - registerTaskStackChangedListener(new TaskStackListener() { - @Override - public void onTaskStackChanged() throws RemoteException { - synchronized (sLock) { - sTaskStackChangedCalled = true; - } - } - }); - - Context ctx = InstrumentationRegistry.getContext(); - ctx.startActivity(new Intent(ctx, ActivityA.class)); - UiDevice.getInstance(getInstrumentation()).waitForIdle(); - synchronized (sLock) { - Assert.assertTrue(sTaskStackChangedCalled); - } - Assert.assertTrue(sActivityBResumed); - } - - @Test - public void testTaskDescriptionChanged() throws Exception { - final Object[] params = new Object[2]; - final CountDownLatch latch = new CountDownLatch(1); - registerTaskStackChangedListener(new TaskStackListener() { - int taskId = -1; - - @Override - public void onTaskCreated(int taskId, ComponentName componentName) - throws RemoteException { - this.taskId = taskId; - } - @Override - public void onTaskDescriptionChanged(int taskId, TaskDescription td) - throws RemoteException { - if (this.taskId == taskId && !TextUtils.isEmpty(td.getLabel())) { - params[0] = taskId; - params[1] = td; - latch.countDown(); - } - } - }); - final Activity activity = startTestActivity(ActivityTaskDescriptionChange.class); - waitForCallback(latch); - assertEquals(activity.getTaskId(), params[0]); - assertEquals("Test Label", ((TaskDescription) params[1]).getLabel()); - } - - @Test - public void testActivityRequestedOrientationChanged() throws Exception { - final int[] params = new int[2]; - final CountDownLatch latch = new CountDownLatch(1); - registerTaskStackChangedListener(new TaskStackListener() { - @Override - public void onActivityRequestedOrientationChanged(int taskId, - int requestedOrientation) { - params[0] = taskId; - params[1] = requestedOrientation; - latch.countDown(); - } - }); - final Activity activity = startTestActivity(ActivityRequestedOrientationChange.class); - waitForCallback(latch); - assertEquals(activity.getTaskId(), params[0]); - assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]); - } - - @Test - /** - * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted. - */ - public void testTaskChangeCallBacks() throws Exception { - final Object[] params = new Object[2]; - final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1); - final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1); - final CountDownLatch taskRemovedLatch = new CountDownLatch(1); - final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1); - final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1); - registerTaskStackChangedListener(new TaskStackListener() { - @Override - public void onTaskCreated(int taskId, ComponentName componentName) - throws RemoteException { - params[0] = taskId; - params[1] = componentName; - taskCreatedLaunchLatch.countDown(); - } - - @Override - public void onTaskMovedToFront(int taskId) throws RemoteException { - params[0] = taskId; - taskMovedToFrontLatch.countDown(); - } - - @Override - public void onTaskRemovalStarted(int taskId) { - params[0] = taskId; - taskRemovalStartedLatch.countDown(); - } - - @Override - public void onTaskRemoved(int taskId) throws RemoteException { - params[0] = taskId; - taskRemovedLatch.countDown(); - } - }); - - final ActivityTaskChangeCallbacks activity = - (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class); - final int id = activity.getTaskId(); - - // Test for onTaskCreated. - waitForCallback(taskCreatedLaunchLatch); - assertEquals(id, params[0]); - ComponentName componentName = (ComponentName) params[1]; - assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName()); - - // Test for onTaskMovedToFront. - assertEquals(1, taskMovedToFrontLatch.getCount()); - mService.moveTaskToFront(id, 0, null); - waitForCallback(taskMovedToFrontLatch); - assertEquals(activity.getTaskId(), params[0]); - - // Test for onTaskRemovalStarted. - assertEquals(1, taskRemovalStartedLatch.getCount()); - activity.finishAndRemoveTask(); - waitForCallback(taskRemovalStartedLatch); - // onTaskRemovalStarted happens before the activity's window is removed. - assertFalse(activity.onDetachedFromWindowCalled); - assertEquals(id, params[0]); - - // Test for onTaskRemoved. - assertEquals(1, taskRemovedLatch.getCount()); - waitForCallback(taskRemovedLatch); - assertEquals(id, params[0]); - assertTrue(activity.onDetachedFromWindowCalled); - } - - /** - * Starts the provided activity and returns the started instance. - */ - private Activity startTestActivity(Class activityClass) { - final Context context = InstrumentationRegistry.getContext(); - final ActivityMonitor monitor = - new ActivityMonitor(activityClass.getName(), null, false); - InstrumentationRegistry.getInstrumentation().addMonitor(monitor); - context.startActivity(new Intent(context, activityClass)); - final Activity activity = monitor.waitForActivityWithTimeout(1000); - if (activity == null) { - throw new RuntimeException("Timed out waiting for Activity"); - } - return activity; - } - - private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception { - mTaskStackListener = listener; - mService.registerTaskStackListener(listener); - } - - private void waitForCallback(CountDownLatch latch) { - try { - final boolean result = latch.await(2, TimeUnit.SECONDS); - if (!result) { - throw new RuntimeException("Timed out waiting for task stack change notification"); - } - }catch (InterruptedException e) {} - } - - public static class ActivityA extends Activity { - - private boolean mActivityBLaunched = false; - - @Override - protected void onPostResume() { - super.onPostResume(); - if (mActivityBLaunched) { - return; - } - mActivityBLaunched = true; - finish(); - startActivity(new Intent(this, ActivityB.class)); - } - } - - public static class ActivityB extends Activity { - - @Override - protected void onPostResume() { - super.onPostResume(); - synchronized (sLock) { - sTaskStackChangedCalled = false; - } - sActivityBResumed = true; - finish(); - } - } - - public static class ActivityRequestedOrientationChange extends Activity { - @Override - protected void onPostResume() { - super.onPostResume(); - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - finish(); - } - } - - public static class ActivityTaskDescriptionChange extends Activity { - @Override - protected void onPostResume() { - super.onPostResume(); - setTaskDescription(new TaskDescription("Test Label")); - finish(); - } - } - - public static class ActivityTaskChangeCallbacks extends Activity { - public boolean onDetachedFromWindowCalled = false;; - - @Override - public void onDetachedFromWindow() { - onDetachedFromWindowCalled = true; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java deleted file mode 100644 index cc4f51987523..000000000000 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.am; - -import android.app.IUserSwitchObserver; -import android.content.Context; -import android.content.IIntentReceiver; -import android.content.Intent; -import android.content.pm.UserInfo; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IRemoteCallback; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.UserManagerInternal; -import android.platform.test.annotations.Presubmit; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; - -import com.android.server.pm.UserManagerService; -import com.android.server.wm.WindowManagerService; - -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; -import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG; -import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG; -import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG; -import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG; -import static com.android.server.am.UserController.SYSTEM_USER_CURRENT_MSG; -import static com.android.server.am.UserController.SYSTEM_USER_START_MSG; -import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; - -/** - * Usage: bit FrameworksServicesTests:com.android.server.am.UserControllerTest - */ -@Presubmit -public class UserControllerTest extends AndroidTestCase { - private static final int TEST_USER_ID = 10; - private static final int NONEXIST_USER_ID = 2; - private static String TAG = UserControllerTest.class.getSimpleName(); - private UserController mUserController; - private TestInjector mInjector; - - private static final List START_FOREGROUND_USER_ACTIONS = - Arrays.asList( - Intent.ACTION_USER_STARTED, - Intent.ACTION_USER_SWITCHED, - Intent.ACTION_USER_STARTING); - - private static final List START_BACKGROUND_USER_ACTIONS = - Arrays.asList( - Intent.ACTION_USER_STARTED, - Intent.ACTION_LOCKED_BOOT_COMPLETED, - Intent.ACTION_USER_STARTING); - - private static final Set START_FOREGROUND_USER_MESSAGE_CODES = - new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, - SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG)); - - private static final Set START_BACKGROUND_USER_MESSAGE_CODES = - new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG)); - - @Override - public void setUp() throws Exception { - super.setUp(); - runWithDexmakerShareClassLoader(() -> { - mInjector = Mockito.spy(new TestInjector(getContext())); - doNothing().when(mInjector).clearAllLockedTasks(anyString()); - doNothing().when(mInjector).startHomeActivity(anyInt(), anyString()); - doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any()); - doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity(); - mUserController = new UserController(mInjector); - setUpUser(TEST_USER_ID, 0); - }); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - mInjector.handlerThread.quit(); - Mockito.validateMockitoUsage(); - } - - @SmallTest - public void testStartUser_foreground() throws RemoteException { - mUserController.startUser(TEST_USER_ID, true /* foreground */); - Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt()); - Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); - Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); - Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true); - Mockito.verify(mInjector).clearAllLockedTasks(anyString()); - startForegroundUserAssertions(); - } - - @SmallTest - public void testStartUser_background() throws RemoteException { - mUserController.startUser(TEST_USER_ID, false /* foreground */); - Mockito.verify( - mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); - Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); - Mockito.verify(mInjector, never()).clearAllLockedTasks(anyString()); - startBackgroundUserAssertions(); - } - - @SmallTest - public void testStartUserUIDisabled() throws RemoteException { - mUserController.mUserSwitchUiEnabled = false; - mUserController.startUser(TEST_USER_ID, true /* foreground */); - Mockito.verify(mInjector.getWindowManager(), never()) - .startFreezingScreen(anyInt(), anyInt()); - Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); - Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); - startForegroundUserAssertions(); - } - - private void startUserAssertions( - List expectedActions, Set expectedMessageCodes) - throws RemoteException { - assertEquals(expectedActions, getActions(mInjector.sentIntents)); - Set actualCodes = mInjector.handler.getMessageCodes(); - assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes); - } - - private void startBackgroundUserAssertions() throws RemoteException { - startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES); - } - - private void startForegroundUserAssertions() throws RemoteException { - startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES); - Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); - assertNotNull(reportMsg); - UserState userState = (UserState) reportMsg.obj; - assertNotNull(userState); - assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier()); - assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); - assertEquals("Unexpected old user id", 0, reportMsg.arg1); - assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2); - } - - @SmallTest - public void testFailedStartUserInForeground() throws RemoteException { - mUserController.mUserSwitchUiEnabled = false; - mUserController.startUserInForeground(NONEXIST_USER_ID); - Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); - Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(false); - } - - @SmallTest - public void testDispatchUserSwitch() throws RemoteException { - // Prepare mock observer and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - doAnswer(invocation -> { - IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; - callback.sendResult(null); - return null; - }).when(observer).onUserSwitching(anyInt(), any()); - mUserController.registerUserSwitchObserver(observer, "mock"); - // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); - Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); - assertNotNull(reportMsg); - UserState userState = (UserState) reportMsg.obj; - int oldUserId = reportMsg.arg1; - int newUserId = reportMsg.arg2; - // Call dispatchUserSwitch and verify that observer was called only once - mInjector.handler.clearAllRecordedMessages(); - mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); - Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); - Set expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG); - Set actualCodes = mInjector.handler.getMessageCodes(); - assertEquals("Unexpected message sent", expectedCodes, actualCodes); - Message conMsg = mInjector.handler.getMessageForCode(CONTINUE_USER_SWITCH_MSG); - assertNotNull(conMsg); - userState = (UserState) conMsg.obj; - assertNotNull(userState); - assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier()); - assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); - assertEquals("Unexpected old user id", 0, conMsg.arg1); - assertEquals("Unexpected new user id", TEST_USER_ID, conMsg.arg2); - } - - @SmallTest - public void testDispatchUserSwitchBadReceiver() throws RemoteException { - // Prepare mock observer which doesn't notify the callback and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - mUserController.registerUserSwitchObserver(observer, "mock"); - // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); - Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); - assertNotNull(reportMsg); - UserState userState = (UserState) reportMsg.obj; - int oldUserId = reportMsg.arg1; - int newUserId = reportMsg.arg2; - // Call dispatchUserSwitch and verify that observer was called only once - mInjector.handler.clearAllRecordedMessages(); - mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); - Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); - // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout) - Set actualCodes = mInjector.handler.getMessageCodes(); - assertTrue("No messages should be sent", actualCodes.isEmpty()); - } - - @SmallTest - public void testContinueUserSwitch() throws RemoteException { - // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); - Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); - assertNotNull(reportMsg); - UserState userState = (UserState) reportMsg.obj; - int oldUserId = reportMsg.arg1; - int newUserId = reportMsg.arg2; - mInjector.handler.clearAllRecordedMessages(); - // Verify that continueUserSwitch worked as expected - mUserController.continueUserSwitch(userState, oldUserId, newUserId); - Mockito.verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); - continueUserSwitchAssertions(); - } - - @SmallTest - public void testContinueUserSwitchUIDisabled() throws RemoteException { - mUserController.mUserSwitchUiEnabled = false; - // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); - Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); - assertNotNull(reportMsg); - UserState userState = (UserState) reportMsg.obj; - int oldUserId = reportMsg.arg1; - int newUserId = reportMsg.arg2; - mInjector.handler.clearAllRecordedMessages(); - // Verify that continueUserSwitch worked as expected - mUserController.continueUserSwitch(userState, oldUserId, newUserId); - Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); - continueUserSwitchAssertions(); - } - - private void continueUserSwitchAssertions() throws RemoteException { - Set expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG); - Set actualCodes = mInjector.handler.getMessageCodes(); - assertEquals("Unexpected message sent", expectedCodes, actualCodes); - Message msg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG); - assertNotNull(msg); - assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1); - } - - @SmallTest - public void testDispatchUserSwitchComplete() throws RemoteException { - // Prepare mock observer and register it - IUserSwitchObserver observer = mock(IUserSwitchObserver.class); - when(observer.asBinder()).thenReturn(new Binder()); - mUserController.registerUserSwitchObserver(observer, "mock"); - // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); - Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); - assertNotNull(reportMsg); - int newUserId = reportMsg.arg2; - mInjector.handler.clearAllRecordedMessages(); - // Mockito can't reset only interactions, so just verify that this hasn't been - // called with 'false' until after dispatchUserSwitchComplete. - Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(false); - // Call dispatchUserSwitchComplete - mUserController.dispatchUserSwitchComplete(newUserId); - Mockito.verify(observer, times(1)).onUserSwitchComplete(anyInt()); - Mockito.verify(observer).onUserSwitchComplete(TEST_USER_ID); - Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false); - } - - private void setUpUser(int userId, int flags) { - UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); - when(mInjector.userManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); - } - - private static List getActions(List intents) { - List result = new ArrayList<>(); - for (Intent intent : intents) { - result.add(intent.getAction()); - } - return result; - } - - // Should be public to allow mocking - public static class TestInjector extends UserController.Injector { - TestHandler handler; - TestHandler uiHandler; - HandlerThread handlerThread; - UserManagerService userManagerMock; - UserManagerInternal userManagerInternalMock; - WindowManagerService windowManagerMock; - private Context mCtx; - List sentIntents = new ArrayList<>(); - - TestInjector(Context ctx) { - super(null); - mCtx = ctx; - handlerThread = new HandlerThread(TAG); - handlerThread.start(); - handler = new TestHandler(handlerThread.getLooper()); - uiHandler = new TestHandler(handlerThread.getLooper()); - userManagerMock = mock(UserManagerService.class); - userManagerInternalMock = mock(UserManagerInternal.class); - windowManagerMock = mock(WindowManagerService.class); - } - - @Override - protected Handler getHandler(Handler.Callback callback) { - return handler; - } - - @Override - protected Handler getUiHandler(Handler.Callback callback) { - return uiHandler; - } - - @Override - protected UserManagerService getUserManager() { - return userManagerMock; - } - - @Override - UserManagerInternal getUserManagerInternal() { - return userManagerInternalMock; - } - - @Override - protected Context getContext() { - return mCtx; - } - - @Override - int checkCallingPermission(String permission) { - Log.i(TAG, "checkCallingPermission " + permission); - return PERMISSION_GRANTED; - } - - @Override - WindowManagerService getWindowManager() { - return windowManagerMock; - } - - @Override - void updateUserConfiguration() { - Log.i(TAG, "updateUserConfiguration"); - } - - @Override - protected int broadcastIntent(Intent intent, String resolvedType, - IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, - String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered, - boolean sticky, int callingPid, int callingUid, int userId) { - Log.i(TAG, "broadcastIntentLocked " + intent); - sentIntents.add(intent); - return 0; - } - - @Override - void reportGlobalUsageEventLocked(int event) { - } - - @Override - void reportCurWakefulnessUsageEvent() { - } - } - - private static class TestHandler extends Handler { - private final List mMessages = new ArrayList<>(); - - TestHandler(Looper looper) { - super(looper); - } - - Set getMessageCodes() { - Set result = new LinkedHashSet<>(); - for (Message msg : mMessages) { - result.add(msg.what); - } - return result; - } - - Message getMessageForCode(int what) { - for (Message msg : mMessages) { - if (msg.what == what) { - return msg; - } - } - return null; - } - - void clearAllRecordedMessages() { - mMessages.clear(); - } - - @Override - public boolean sendMessageAtTime(Message msg, long uptimeMillis) { - Message copy = new Message(); - copy.copyFrom(msg); - mMessages.add(copy); - return super.sendMessageAtTime(msg, uptimeMillis); - } - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java deleted file mode 100644 index 7487d4490d9a..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import android.annotation.Nullable; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.proto.ProtoOutputStream; -import android.view.Display; -import android.view.DisplayCutout; -import android.view.IApplicationToken; -import android.view.WindowManager; - -import com.android.server.wm.utils.WmDisplayCutout; - -public class FakeWindowState implements WindowManagerPolicy.WindowState { - - public final Rect parentFrame = new Rect(); - public final Rect displayFrame = new Rect(); - public final Rect overscanFrame = new Rect(); - public final Rect contentFrame = new Rect(); - public final Rect visibleFrame = new Rect(); - public final Rect decorFrame = new Rect(); - public final Rect stableFrame = new Rect(); - public Rect outsetFrame = new Rect(); - - public WmDisplayCutout displayCutout; - - public WindowManager.LayoutParams attrs; - public int displayId; - public boolean isVoiceInteraction; - public boolean inMultiWindowMode; - public boolean visible = true; - public int surfaceLayer = 1; - public boolean isDimming = false; - - public boolean policyVisible = true; - - @Override - public int getOwningUid() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public String getOwningPackage() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, - Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame, - @Nullable Rect outsetFrame, WmDisplayCutout displayCutout, - boolean parentFrameWasClippedByDisplayCutout) { - this.parentFrame.set(parentFrame); - this.displayFrame.set(displayFrame); - this.overscanFrame.set(overlayFrame); - this.contentFrame.set(contentFrame); - this.visibleFrame.set(visibleFrame); - this.decorFrame.set(decorFrame); - this.stableFrame.set(stableFrame); - this.outsetFrame = outsetFrame == null ? null : new Rect(outsetFrame); - this.displayCutout = displayCutout; - } - - @Override - public Rect getFrameLw() { - return parentFrame; - } - - @Override - public Rect getDisplayFrameLw() { - return displayFrame; - } - - @Override - public Rect getOverscanFrameLw() { - return overscanFrame; - } - - @Override - public Rect getContentFrameLw() { - return contentFrame; - } - - @Override - public Rect getVisibleFrameLw() { - return visibleFrame; - } - - @Override - public boolean getGivenInsetsPendingLw() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public Rect getGivenContentInsetsLw() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public Rect getGivenVisibleInsetsLw() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public WindowManager.LayoutParams getAttrs() { - return attrs; - } - - @Override - public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public int getSystemUiVisibility() { - return attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility; - } - - @Override - public int getSurfaceLayer() { - return surfaceLayer; - } - - @Override - public int getBaseType() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public IApplicationToken getAppToken() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isVoiceInteraction() { - return isVoiceInteraction; - } - - @Override - public boolean hasAppShownWindows() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isVisibleLw() { - return visible && policyVisible; - } - - @Override - public boolean isDisplayedLw() { - return isVisibleLw(); - } - - @Override - public boolean isAnimatingLw() { - return false; - } - - @Override - public boolean canAffectSystemUiFlags() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isGoneForLayoutLw() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isDrawnLw() { - return true; - } - - @Override - public boolean hasDrawnLw() { - return true; - } - - @Override - public boolean hideLw(boolean doAnimation) { - if (!policyVisible) { - return false; - } - policyVisible = false; - return true; - } - - @Override - public boolean showLw(boolean doAnimation) { - if (policyVisible) { - return false; - } - policyVisible = true; - return true; - } - - @Override - public boolean isAlive() { - return true; - } - - @Override - public boolean isDefaultDisplay() { - return displayId == Display.DEFAULT_DISPLAY; - } - - @Override - public boolean isDimming() { - return isDimming; - } - - @Override - public int getWindowingMode() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isInMultiWindowMode() { - return inMultiWindowMode; - } - - @Override - public int getRotationAnimationHint() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isInputMethodWindow() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public int getDisplayId() { - return displayId; - } - - @Override - public boolean canAcquireSleepToken() { - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId){ - throw new UnsupportedOperationException("not implemented"); - } - - @Override - public boolean isInputMethodTarget() { - return false; - } -} diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java deleted file mode 100644 index 7e18ce78c9d1..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; - -import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.Display; -import android.view.DisplayInfo; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ErrorCollector; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class PhoneWindowManagerInsetsTest extends PhoneWindowManagerTestBase { - - @Rule - public final ErrorCollector mErrorCollector = new ErrorCollector(); - - @Before - public void setUp() throws Exception { - addStatusBar(); - addNavigationBar(); - } - - @Test - public void portrait() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */); - - verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); - verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT); - verifyConsistency(di); - } - - @Test - public void portrait_withCutout() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */); - - verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); - verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT); - verifyConsistency(di); - } - - @Test - public void landscape() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */); - - verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - verifyNonDecorInsets(di, 0, 0, NAV_BAR_HEIGHT, 0); - verifyConsistency(di); - } - - @Test - public void landscape_withCutout() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */); - - verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - verifyNonDecorInsets(di, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT, 0); - verifyConsistency(di); - } - - @Test - public void seascape() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */); - - verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); - verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, 0, 0); - verifyConsistency(di); - } - - @Test - public void seascape_withCutout() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */); - - verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); - verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, DISPLAY_CUTOUT_HEIGHT, 0); - verifyConsistency(di); - } - - @Test - public void upsideDown() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */); - - verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); - verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT); - verifyConsistency(di); - } - - @Test - public void upsideDown_withCutout() throws Exception { - DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */); - - verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT); - verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT); - verifyConsistency(di); - } - - private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) { - mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect( - left, top, right, bottom))); - } - - private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) { - mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(di), equalTo(new Rect( - left, top, right, bottom))); - } - - private void verifyConsistency(DisplayInfo di) { - verifyConsistency("configDisplay", di, getStableInsetsLw(di), - getConfigDisplayWidth(di), getConfigDisplayHeight(di)); - verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di), - getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di)); - } - - private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width, - int height) { - mErrorCollector.checkThat(what + ":width", width, - equalTo(di.logicalWidth - insets.left - insets.right)); - mErrorCollector.checkThat(what + ":height", height, - equalTo(di.logicalHeight - insets.top - insets.bottom)); - } - - private Rect getStableInsetsLw(DisplayInfo di) { - Rect result = new Rect(); - mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - di.displayCutout, result); - return result; - } - - private Rect getNonDecorInsetsLw(DisplayInfo di) { - Rect result = new Rect(); - mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, - di.displayCutout, result); - return result; - } - - private int getNonDecorDisplayWidth(DisplayInfo di) { - return mPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight, di.rotation, - 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); - } - - private int getNonDecorDisplayHeight(DisplayInfo di) { - return mPolicy.getNonDecorDisplayHeight(di.logicalWidth, di.logicalHeight, di.rotation, - 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); - } - - private int getConfigDisplayWidth(DisplayInfo di) { - return mPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight, di.rotation, - 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); - } - - private int getConfigDisplayHeight(DisplayInfo di) { - return mPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight, di.rotation, - 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java deleted file mode 100644 index 97a716f6bd99..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; -import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; -import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.DisplayCutout; -import android.view.WindowManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { - - private FakeWindowState mAppWindow; - - @Before - public void setUp() throws Exception { - mAppWindow = new FakeWindowState(); - mAppWindow.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, - TYPE_APPLICATION, - FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - PixelFormat.TRANSLUCENT); - - addStatusBar(); - addNavigationBar(); - } - - @Test - public void layoutWindowLw_appDrawsBars() throws Exception { - mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); - assertInsetBy(mAppWindow.displayFrame, 0, 0, 0, 0); - } - - @Test - public void layoutWindowLw_appWontDrawBars() throws Exception { - mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.displayFrame, 0, NAV_BAR_HEIGHT); - } - - @Test - public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception { - mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.displayFrame, 0, NAV_BAR_HEIGHT); - } - - @Test - public void addingWindow_doesNotTamperWithSysuiFlags() { - mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mPolicy.addWindow(mAppWindow); - - assertEquals(0, mAppWindow.attrs.systemUiVisibility); - assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility); - } - - @Test - public void layoutWindowLw_withDisplayCutout() { - addDisplayCutout(); - - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.displayFrame, 0, 0); - } - - @Test - public void layoutWindowLw_withhDisplayCutout_never() { - addDisplayCutout(); - - mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, 0); - } - - @Test - public void layoutWindowLw_withDisplayCutout_layoutFullscreen() { - addDisplayCutout(); - - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); - assertInsetBy(mAppWindow.displayFrame, 0, 0, 0, 0); - } - - @Test - public void layoutWindowLw_withDisplayCutout_fullscreen() { - addDisplayCutout(); - - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, 0); - } - - @Test - public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { - addDisplayCutout(); - - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; - mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); - assertInsetByTopBottom(mAppWindow.displayFrame, 0, 0); - } - - - @Test - public void layoutWindowLw_withDisplayCutout_landscape() { - addDisplayCutout(); - setRotation(ROTATION_90); - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); - assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mAppWindow.contentFrame, - DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); - assertInsetBy(mAppWindow.displayFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); - } - - @Test - public void layoutWindowLw_withDisplayCutout_seascape() { - addDisplayCutout(); - setRotation(ROTATION_270); - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetBy(mAppWindow.parentFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); - assertInsetBy(mAppWindow.stableFrame, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); - assertInsetBy(mAppWindow.contentFrame, - NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); - assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); - assertInsetBy(mAppWindow.displayFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); - } - - @Test - public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() { - addDisplayCutout(); - setRotation(ROTATION_90); - - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); - assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mAppWindow.contentFrame, - DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); - } - - @Test - public void layoutWindowLw_withDisplayCutout_floatingInScreen() { - addDisplayCutout(); - - mAppWindow.attrs.flags = FLAG_LAYOUT_IN_SCREEN; - mAppWindow.attrs.type = TYPE_APPLICATION_OVERLAY; - mAppWindow.attrs.width = DISPLAY_WIDTH; - mAppWindow.attrs.height = DISPLAY_HEIGHT; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - } - - @Test - public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() { - addDisplayCutout(); - setRotation(ROTATION_90); - - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mPolicy.addWindow(mAppWindow); - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mPolicy.layoutWindowLw(mAppWindow, null, mFrames); - - assertInsetBy(mAppWindow.parentFrame, 0, 0, 0, 0); - assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mAppWindow.contentFrame, - DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); - assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); - } - - @Test - public void layoutHint_screenDecorWindow() { - addDisplayCutout(); - mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_IS_SCREEN_DECOR; - - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - - final Rect frame = new Rect(); - final Rect content = new Rect(); - final Rect stable = new Rect(); - final Rect outsets = new Rect(); - final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper(); - mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content, - stable, outsets, cutout); - - assertThat(frame, equalTo(mFrames.mUnrestricted)); - assertThat(content, equalTo(new Rect())); - assertThat(stable, equalTo(new Rect())); - assertThat(outsets, equalTo(new Rect())); - assertThat(cutout.get(), equalTo(DisplayCutout.NO_CUTOUT)); - } - - @Test - public void layoutHint_appWindow() { - // Initialize DisplayFrames - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - - final Rect outFrame = new Rect(); - final Rect outContentInsets = new Rect(); - final Rect outStableInsets = new Rect(); - final Rect outOutsets = new Rect(); - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - - mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets, - outStableInsets, outOutsets, outDisplayCutout); - - assertThat(outFrame, is(mFrames.mUnrestricted)); - assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); - assertThat(outStableInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); - assertThat(outOutsets, is(new Rect())); - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); - } - - @Test - public void layoutHint_appWindowInTask() { - // Initialize DisplayFrames - mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - - final Rect taskBounds = new Rect(100, 100, 200, 200); - - final Rect outFrame = new Rect(); - final Rect outContentInsets = new Rect(); - final Rect outStableInsets = new Rect(); - final Rect outOutsets = new Rect(); - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - - mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets, - outStableInsets, outOutsets, outDisplayCutout); - - assertThat(outFrame, is(taskBounds)); - assertThat(outContentInsets, is(new Rect())); - assertThat(outStableInsets, is(new Rect())); - assertThat(outOutsets, is(new Rect())); - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java deleted file mode 100644 index 30665b56c8dc..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTest.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_RIGHT; -import static android.view.WindowManager.DOCKED_TOP; - -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; -import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.graphics.PixelFormat; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.WindowManager; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class PhoneWindowManagerTest { - - private static FakeWindowState createOpaqueFullscreen(boolean hasLightNavBar) { - final FakeWindowState state = new FakeWindowState(); - state.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, - TYPE_BASE_APPLICATION, - FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - PixelFormat.OPAQUE); - state.attrs.subtreeSystemUiVisibility = - hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0; - return state; - } - - private static FakeWindowState createDimmingDialogWindow(boolean canBeImTarget) { - final FakeWindowState state = new FakeWindowState(); - state.attrs = new WindowManager.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, - TYPE_APPLICATION, - FLAG_DIM_BEHIND | (canBeImTarget ? 0 : FLAG_ALT_FOCUSABLE_IM), - PixelFormat.TRANSLUCENT); - state.isDimming = true; - return state; - } - - private static FakeWindowState createInputMethodWindow(boolean visible, boolean drawNavBar, - boolean hasLightNavBar) { - final FakeWindowState state = new FakeWindowState(); - state.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, - TYPE_INPUT_METHOD, - FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN - | (drawNavBar ? FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS : 0), - PixelFormat.TRANSPARENT); - state.attrs.subtreeSystemUiVisibility = - hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0; - state.visible = visible; - state.policyVisible = visible; - return state; - } - - - @Test - public void testChooseNavigationColorWindowLw() throws Exception { - final FakeWindowState opaque = createOpaqueFullscreen(false); - - final FakeWindowState dimmingImTarget = createDimmingDialogWindow(true); - final FakeWindowState dimmingNonImTarget = createDimmingDialogWindow(false); - - final FakeWindowState visibleIme = createInputMethodWindow(true, true, false); - final FakeWindowState invisibleIme = createInputMethodWindow(false, true, false); - final FakeWindowState imeNonDrawNavBar = createInputMethodWindow(true, false, false); - - // If everything is null, return null - assertNull(null, PhoneWindowManager.chooseNavigationColorWindowLw( - null, null, null, NAV_BAR_BOTTOM)); - - assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, opaque, null, NAV_BAR_BOTTOM)); - assertEquals(dimmingImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, dimmingImTarget, null, NAV_BAR_BOTTOM)); - assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, dimmingNonImTarget, null, NAV_BAR_BOTTOM)); - - assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( - null, null, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( - null, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( - null, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, opaque, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); - assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); - - assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, opaque, invisibleIme, NAV_BAR_BOTTOM)); - assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, opaque, invisibleIme, NAV_BAR_BOTTOM)); - assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, opaque, visibleIme, NAV_BAR_RIGHT)); - - // Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color - // window. - assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, opaque, imeNonDrawNavBar, NAV_BAR_BOTTOM)); - assertEquals(dimmingImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); - assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( - opaque, dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); - } - - @Test - public void testUpdateLightNavigationBarLw() throws Exception { - final FakeWindowState opaqueDarkNavBar = createOpaqueFullscreen(false); - final FakeWindowState opaqueLightNavBar = createOpaqueFullscreen(true); - - final FakeWindowState dimming = createDimmingDialogWindow(false); - - final FakeWindowState imeDrawDarkNavBar = createInputMethodWindow(true,true, false); - final FakeWindowState imeDrawLightNavBar = createInputMethodWindow(true,true, true); - - assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, - PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, - null, null)); - - // Opaque top fullscreen window overrides SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR flag. - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - 0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar)); - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, opaqueDarkNavBar, null, - opaqueDarkNavBar)); - assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, - PhoneWindowManager.updateLightNavigationBarLw(0, opaqueLightNavBar, - opaqueLightNavBar, null, opaqueLightNavBar)); - assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, - PhoneWindowManager.updateLightNavigationBarLw(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, - opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar)); - - // Dimming window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR. - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - 0, opaqueDarkNavBar, dimming, null, dimming)); - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - 0, opaqueLightNavBar, dimming, null, dimming)); - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, dimming, null, dimming)); - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, null, dimming)); - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, imeDrawLightNavBar, - dimming)); - - // IME window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, imeDrawDarkNavBar, - imeDrawDarkNavBar)); - - // Even if the top fullscreen has SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, IME window wins. - assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( - SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, opaqueLightNavBar, - imeDrawDarkNavBar, imeDrawDarkNavBar)); - - // IME window should be able to use SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR. - assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, - PhoneWindowManager.updateLightNavigationBarLw(0, opaqueDarkNavBar, - opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar)); - } - - @Test - public void testIsDockSideAllowedDockTop() throws Exception { - // Docked top is always allowed - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_TOP, DOCKED_LEFT, NAV_BAR_BOTTOM, - true /* navigationBarCanMove */)); - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_TOP, DOCKED_LEFT, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - } - - @Test - public void testIsDockSideAllowedDockBottom() throws Exception { - // Cannot dock bottom - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_BOTTOM, DOCKED_LEFT, NAV_BAR_BOTTOM, - true /* navigationBarCanMove */)); - } - - @Test - public void testIsDockSideAllowedNavigationBarMovable() throws Exception { - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_BOTTOM, - true /* navigationBarCanMove */)); - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_LEFT, - true /* navigationBarCanMove */)); - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_RIGHT, - true /* navigationBarCanMove */)); - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_BOTTOM, - true /* navigationBarCanMove */)); - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_RIGHT, - true /* navigationBarCanMove */)); - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_LEFT, - true /* navigationBarCanMove */)); - } - - @Test - public void testIsDockSideAllowedNavigationBarNotMovable() throws Exception { - // Navigation bar is not movable such as tablets - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_TOP, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_RIGHT, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_TOP, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_RIGHT, NAV_BAR_BOTTOM, - false /* navigationBarCanMove */)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java deleted file mode 100644 index 2c47a9432eff..000000000000 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.policy; - -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; - -import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; - -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Matrix; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.IBinder; -import android.os.UserHandle; -import android.support.test.InstrumentationRegistry; -import android.testing.TestableResources; -import android.util.Pair; -import android.view.Display; -import android.view.DisplayCutout; -import android.view.DisplayInfo; -import android.view.Gravity; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.IAccessibilityManager; - -import com.android.server.policy.keyguard.KeyguardServiceDelegate; -import com.android.server.wm.DisplayFrames; -import com.android.server.wm.utils.WmDisplayCutout; - -import org.junit.Before; - -public class PhoneWindowManagerTestBase { - static final int DISPLAY_WIDTH = 500; - static final int DISPLAY_HEIGHT = 1000; - - static final int STATUS_BAR_HEIGHT = 10; - static final int NAV_BAR_HEIGHT = 15; - static final int DISPLAY_CUTOUT_HEIGHT = 8; - - TestablePhoneWindowManager mPolicy; - TestContextWrapper mContext; - DisplayFrames mFrames; - - FakeWindowState mStatusBar; - FakeWindowState mNavigationBar; - private boolean mHasDisplayCutout; - private int mRotation = ROTATION_0; - - @Before - public void setUpBase() throws Exception { - mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext()); - mContext.getResourceMocker().addOverride( - com.android.internal.R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT); - mContext.getResourceMocker().addOverride( - com.android.internal.R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT); - mContext.getResourceMocker().addOverride( - com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT); - mContext.getResourceMocker().addOverride( - com.android.internal.R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT); - mContext.getResourceMocker().addOverride( - com.android.internal.R.dimen.navigation_bar_width, NAV_BAR_HEIGHT); - - mPolicy = TestablePhoneWindowManager.create(mContext); - - updateDisplayFrames(); - } - - public void setRotation(int rotation) { - mRotation = rotation; - updateDisplayFrames(); - } - - private void updateDisplayFrames() { - Pair info = displayInfoAndCutoutForRotation(mRotation, - mHasDisplayCutout); - mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info.first, info.second); - } - - public void addStatusBar() { - mStatusBar = new FakeWindowState(); - mStatusBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT, - TYPE_STATUS_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); - mStatusBar.attrs.gravity = Gravity.TOP; - - mPolicy.addWindow(mStatusBar); - mPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT; - } - - public void addNavigationBar() { - mNavigationBar = new FakeWindowState(); - mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT, - TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); - mNavigationBar.attrs.gravity = Gravity.BOTTOM; - - mPolicy.addWindow(mNavigationBar); - mPolicy.mHasNavigationBar = true; - mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT; - } - - public void addDisplayCutout() { - mHasDisplayCutout = true; - updateDisplayFrames(); - } - - /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */ - public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop, - int expectedInsetRight, int expectedInsetBottom) { - assertEquals(new Rect(expectedInsetLeft, expectedInsetTop, - mFrames.mDisplayWidth - expectedInsetRight, - mFrames.mDisplayHeight - expectedInsetBottom), actual); - } - - /** - * Asserts that {@code actual} is inset by the given amounts from the full display rect. - * - * Convenience wrapper for when only the top and bottom inset are non-zero. - */ - public void assertInsetByTopBottom(Rect actual, int expectedInsetTop, int expectedInsetBottom) { - assertInsetBy(actual, 0, expectedInsetTop, 0, expectedInsetBottom); - } - - public static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) { - return displayInfoAndCutoutForRotation(rotation, withDisplayCutout).first; - } - public static Pair displayInfoAndCutoutForRotation(int rotation, - boolean withDisplayCutout) { - DisplayInfo info = new DisplayInfo(); - WmDisplayCutout cutout = null; - - final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270; - info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH; - info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT; - info.rotation = rotation; - if (withDisplayCutout) { - cutout = WmDisplayCutout.computeSafeInsets( - displayCutoutForRotation(rotation), info.logicalWidth, - info.logicalHeight); - info.displayCutout = cutout.getDisplayCutout(); - } else { - info.displayCutout = null; - } - return Pair.create(info, cutout); - } - - private static DisplayCutout displayCutoutForRotation(int rotation) { - Path p = new Path(); - p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT, - Path.Direction.CCW); - - Matrix m = new Matrix(); - transformPhysicalToLogicalCoordinates(rotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, m); - p.transform(m); - - return DisplayCutout.fromBounds(p); - } - - static class TestContextWrapper extends ContextWrapper { - private final TestableResources mResourceMocker; - - public TestContextWrapper(Context targetContext) { - super(targetContext); - mResourceMocker = new TestableResources(targetContext.getResources()); - } - - @Override - public int checkPermission(String permission, int pid, int uid) { - return PackageManager.PERMISSION_GRANTED; - } - - @Override - public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { - return PackageManager.PERMISSION_GRANTED; - } - - @Override - public Resources getResources() { - return mResourceMocker.getResources(); - } - - public TestableResources getResourceMocker() { - return mResourceMocker; - } - } - - static class TestablePhoneWindowManager extends PhoneWindowManager { - - public TestablePhoneWindowManager() { - } - - @Override - void initializeHdmiState() { - // Do nothing. - } - - @Override - Context getSystemUiContext() { - return mContext; - } - - void addWindow(WindowState state) { - if (state instanceof FakeWindowState) { - ((FakeWindowState) state).surfaceLayer = - getWindowLayerFromTypeLw(state.getAttrs().type, - true /* canAddInternalSystemWindow */); - } - adjustWindowParamsLw(state, state.getAttrs(), true /* hasStatusBarPermission */); - assertEquals(WindowManagerGlobal.ADD_OKAY, prepareAddWindowLw(state, state.getAttrs())); - } - - public static TestablePhoneWindowManager create(Context context) { - TestablePhoneWindowManager[] policy = new TestablePhoneWindowManager[1]; - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - policy[0] = new TestablePhoneWindowManager(); - policy[0].mContext = context; - policy[0].mKeyguardDelegate = mock(KeyguardServiceDelegate.class); - policy[0].mAccessibilityManager = new AccessibilityManager(context, - mock(IAccessibilityManager.class), UserHandle.USER_CURRENT); - policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class); - policy[0].mNavigationBarCanMove = true; - policy[0].mPortraitRotation = ROTATION_0; - policy[0].mLandscapeRotation = ROTATION_90; - policy[0].mUpsideDownRotation = ROTATION_180; - policy[0].mSeascapeRotation = ROTATION_270; - policy[0].onConfigurationChanged(); - }); - return policy[0]; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java b/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java deleted file mode 100644 index 164c80b2427a..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for the {@link TaskStack} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.wm.AnimatingAppWindowTokenRegistryTest - */ -@SmallTest -@Presubmit -@FlakyTest(detail = "Promote once confirmed non-flaky") -@RunWith(AndroidJUnit4.class) -public class AnimatingAppWindowTokenRegistryTest extends WindowTestsBase { - - @Mock - AnimationAdapter mAdapter; - - @Mock - Runnable mMockEndDeferFinishCallback1; - @Mock - Runnable mMockEndDeferFinishCallback2; - @Before - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - } - - @Test - public void testDeferring() throws Exception { - final AppWindowToken window1 = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD, - "window2").mAppToken; - final AnimatingAppWindowTokenRegistry registry = - window1.getStack().getAnimatingAppWindowTokenRegistry(); - - window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); - window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); - assertTrue(window1.isSelfAnimating()); - assertTrue(window2.isSelfAnimating()); - - // Make sure that first animation finish is deferred, second one is not deferred, and first - // one gets cancelled. - assertTrue(registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1)); - assertFalse(registry.notifyAboutToFinish(window2, mMockEndDeferFinishCallback2)); - verify(mMockEndDeferFinishCallback1).run(); - verifyZeroInteractions(mMockEndDeferFinishCallback2); - } - - @Test - public void testContainerRemoved() throws Exception { - final AppWindowToken window1 = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD, - "window2").mAppToken; - final AnimatingAppWindowTokenRegistry registry = - window1.getStack().getAnimatingAppWindowTokenRegistry(); - - window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); - window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); - assertTrue(window1.isSelfAnimating()); - assertTrue(window2.isSelfAnimating()); - - // Make sure that first animation finish is deferred, and removing the second window stops - // finishes all pending deferred finishings. - registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1); - window2.setParent(null); - verify(mMockEndDeferFinishCallback1).run(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java deleted file mode 100644 index be7d781799fa..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/AppTransitionTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; -import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE; -import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; -import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; -import static org.junit.Assert.assertEquals; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link AppTransition}. - * - * atest AppTransitionTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class AppTransitionTests { - - @Rule - public final WindowManagerServiceRule mRule = new WindowManagerServiceRule(); - private WindowManagerService mWm; - - @Before - public void setUp() throws Exception { - final Context context = InstrumentationRegistry.getTargetContext(); - mWm = mRule.getWindowManagerService(); - } - - @Test - public void testKeyguardOverride() throws Exception { - mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); - mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); - assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition()); - } - - @Test - public void testKeyguardKeep() throws Exception { - mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); - mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); - assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition()); - } - - @Test - public void testForceOverride() throws Exception { - mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */); - mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */, - 0 /* flags */, true /* forceOverride */); - assertEquals(TRANSIT_ACTIVITY_OPEN, mWm.mAppTransition.getAppTransition()); - } - - @Test - public void testCrashing() throws Exception { - mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); - mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); - assertEquals(TRANSIT_CRASHING_ACTIVITY_CLOSE, mWm.mAppTransition.getAppTransition()); - } - - @Test - public void testKeepKeyguard_withCrashing() throws Exception { - mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); - mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); - assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java deleted file mode 100644 index e0645b1f4bfb..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.support.test.filters.FlakyTest; -import org.junit.Test; - -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.res.Configuration.EMPTY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - -import com.android.server.wm.WindowTestUtils.TestTaskWindowContainerController; - -/** - * Test class for {@link AppWindowContainerController}. - * - * atest FrameworksServicesTests:com.android.server.wm.AppWindowContainerControllerTests - */ -@SmallTest -@Presubmit -@FlakyTest(bugId = 74078662) -@org.junit.runner.RunWith(AndroidJUnit4.class) -public class AppWindowContainerControllerTests extends WindowTestsBase { - - @Test - public void testRemoveContainer() throws Exception { - final WindowTestUtils.TestAppWindowContainerController controller = - createAppWindowController(); - - // Assert token was added to display. - assertNotNull(mDisplayContent.getWindowToken(controller.mToken.asBinder())); - // Assert that the container was created and linked. - assertNotNull(controller.mContainer); - - controller.removeContainer(mDisplayContent.getDisplayId()); - - // Assert token was remove from display. - assertNull(mDisplayContent.getWindowToken(controller.mToken.asBinder())); - // Assert that the container was removed. - assertNull(controller.mContainer); - } - - @Test - public void testSetOrientation() throws Exception { - final WindowTestUtils.TestAppWindowContainerController controller = - createAppWindowController(); - - // Assert orientation is unspecified to start. - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation()); - - controller.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getDisplayId(), - EMPTY /* displayConfig */, false /* freezeScreenIfNeeded */); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, controller.getOrientation()); - - controller.removeContainer(mDisplayContent.getDisplayId()); - // Assert orientation is unspecified to after container is removed. - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation()); - - // Reset display frozen state - sWm.mDisplayFrozen = false; - } - - private void assertHasStartingWindow(AppWindowToken atoken) { - assertNotNull(atoken.startingSurface); - assertNotNull(atoken.startingData); - assertNotNull(atoken.startingWindow); - } - - private void assertNoStartingWindow(AppWindowToken atoken) { - assertNull(atoken.startingSurface); - assertNull(atoken.startingWindow); - assertNull(atoken.startingData); - atoken.forAllWindows(windowState -> { - assertFalse(windowState.getBaseType() == TYPE_APPLICATION_STARTING); - }, true); - } - - @Test - public void testCreateRemoveStartingWindow() throws Exception { - final WindowTestUtils.TestAppWindowContainerController controller = - createAppWindowController(); - controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false, false); - waitUntilHandlersIdle(); - final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent); - assertHasStartingWindow(atoken); - controller.removeStartingWindow(); - waitUntilHandlersIdle(); - assertNoStartingWindow(atoken); - } - - @Test - public void testAddRemoveRace() throws Exception { - - // There was once a race condition between adding and removing starting windows - for (int i = 0; i < 1000; i++) { - final WindowTestUtils.TestAppWindowContainerController controller = - createAppWindowController(); - controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false, false); - controller.removeStartingWindow(); - waitUntilHandlersIdle(); - assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent)); - - controller.getAppWindowToken(mDisplayContent).getParent().getParent().removeImmediately(); - } - } - - @Test - public void testTransferStartingWindow() throws Exception { - final WindowTestUtils.TestAppWindowContainerController controller1 = - createAppWindowController(); - final WindowTestUtils.TestAppWindowContainerController controller2 = - createAppWindowController(); - controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false, false); - waitUntilHandlersIdle(); - controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), - true, true, false, true, false, false); - waitUntilHandlersIdle(); - assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent)); - assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent)); - } - - @Test - public void testTransferStartingWindowWhileCreating() throws Exception { - final WindowTestUtils.TestAppWindowContainerController controller1 = - createAppWindowController(); - final WindowTestUtils.TestAppWindowContainerController controller2 = - createAppWindowController(); - ((TestWindowManagerPolicy) sWm.mPolicy).setRunnableWhenAddingSplashScreen(() -> { - - // Surprise, ...! Transfer window in the middle of the creation flow. - controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), - true, true, false, true, false, false); - }); - controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false, false); - waitUntilHandlersIdle(); - assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent)); - assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent)); - } - - @Test - public void testTryTransferStartingWindowFromHiddenAboveToken() throws Exception { - - // Add two tasks on top of each other. - TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController controllerTop = - createAppWindowController(taskController); - final WindowTestUtils.TestAppWindowContainerController controllerBottom = - createAppWindowController(taskController); - - // Add a starting window. - controllerTop.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), - android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, - false, false); - waitUntilHandlersIdle(); - - // Make the top one invisible, and try transfering the starting window from the top to the - // bottom one. - controllerTop.setVisibility(false, false); - controllerBottom.mContainer.transferStartingWindowFromHiddenAboveTokenIfNeeded(); - - // Assert that the bottom window now has the starting window. - assertNoStartingWindow(controllerTop.getAppWindowToken(mDisplayContent)); - assertHasStartingWindow(controllerBottom.getAppWindowToken(mDisplayContent)); - } - - @Test - public void testReparent() throws Exception { - final StackWindowController stackController = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController1 = - new WindowTestUtils.TestTaskWindowContainerController(stackController); - final WindowTestUtils.TestAppWindowContainerController appWindowController1 = - createAppWindowController(taskController1); - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stackController); - final WindowTestUtils.TestAppWindowContainerController appWindowController2 = - createAppWindowController(taskController2); - final WindowTestUtils.TestTaskWindowContainerController taskController3 = - new WindowTestUtils.TestTaskWindowContainerController(stackController); - - try { - appWindowController1.reparent(taskController1, 0); - fail("Should not be able to reparent to the same parent"); - } catch (IllegalArgumentException e) { - // Expected - } - - try { - taskController3.setContainer(null); - appWindowController1.reparent(taskController3, 0); - fail("Should not be able to reparent to a task that doesn't have a container"); - } catch (IllegalArgumentException e) { - // Expected - } - - // Reparent the app window and ensure that it is moved - appWindowController1.reparent(taskController2, 0); - assertEquals(taskController2.mContainer, appWindowController1.mContainer.getParent()); - assertEquals(0, ((WindowTestUtils.TestAppWindowToken) appWindowController1.mContainer) - .positionInParent()); - assertEquals(1, ((WindowTestUtils.TestAppWindowToken) appWindowController2.mContainer) - .positionInParent()); - } - - private WindowTestUtils.TestAppWindowContainerController createAppWindowController() { - return createAppWindowController( - new WindowTestUtils.TestTaskWindowContainerController(this)); - } - - private WindowTestUtils.TestAppWindowContainerController createAppWindowController( - WindowTestUtils.TestTaskWindowContainerController taskController) { - return new WindowTestUtils.TestAppWindowContainerController(taskController); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java deleted file mode 100644 index f6599dcaf87e..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.Surface; -import android.view.WindowManager; - -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; -import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.TRANSIT_UNSET; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -/** - * Tests for the {@link AppWindowToken} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.wm.AppWindowTokenTests - */ -@SmallTest -// TODO: b/68267650 -// @Presubmit -@RunWith(AndroidJUnit4.class) -public class AppWindowTokenTests extends WindowTestsBase { - - TaskStack mStack; - Task mTask; - WindowTestUtils.TestAppWindowToken mToken; - - @Override - public void setUp() throws Exception { - super.setUp(); - - mStack = createTaskStackOnDisplay(mDisplayContent); - mTask = createTaskInStack(mStack, 0 /* userId */); - mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - - mTask.addChild(mToken, 0); - } - - @Test - @Presubmit - public void testAddWindow_Order() throws Exception { - assertEquals(0, mToken.getWindowsCount()); - - final WindowState win1 = createWindow(null, TYPE_APPLICATION, mToken, "win1"); - final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mToken, - "startingWin"); - final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mToken, "baseWin"); - final WindowState win4 = createWindow(null, TYPE_APPLICATION, mToken, "win4"); - - // Should not contain the windows that were added above. - assertEquals(4, mToken.getWindowsCount()); - assertTrue(mToken.hasWindow(win1)); - assertTrue(mToken.hasWindow(startingWin)); - assertTrue(mToken.hasWindow(baseWin)); - assertTrue(mToken.hasWindow(win4)); - - // The starting window should be on-top of all other windows. - assertEquals(startingWin, mToken.getLastChild()); - - // The base application window should be below all other windows. - assertEquals(baseWin, mToken.getFirstChild()); - mToken.removeImmediately(); - } - - @Test - @Presubmit - public void testFindMainWindow() throws Exception { - assertNull(mToken.findMainWindow()); - - final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1"); - final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window11"); - final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window12"); - assertEquals(window1, mToken.findMainWindow()); - window1.mAnimatingExit = true; - assertEquals(window1, mToken.findMainWindow()); - final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mToken, "window2"); - assertEquals(window2, mToken.findMainWindow()); - mToken.removeImmediately(); - } - - @Test - @Presubmit - public void testGetTopFullscreenWindow() throws Exception { - assertNull(mToken.getTopFullscreenWindow()); - - final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1"); - final WindowState window11 = createWindow(null, TYPE_APPLICATION, mToken, "window11"); - final WindowState window12 = createWindow(null, TYPE_APPLICATION, mToken, "window12"); - assertEquals(window12, mToken.getTopFullscreenWindow()); - window12.mAttrs.width = 500; - assertEquals(window11, mToken.getTopFullscreenWindow()); - window11.mAttrs.width = 500; - assertEquals(window1, mToken.getTopFullscreenWindow()); - mToken.removeImmediately(); - } - - @Test - public void testLandscapeSeascapeRotationByApp() throws Exception { - // Some plumbing to get the service ready for rotation updates. - sWm.mDisplayReady = true; - sWm.mDisplayEnabled = true; - - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( - TYPE_BASE_APPLICATION); - attrs.setTitle("AppWindow"); - final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); - mToken.addWindow(appWindow); - - // Set initial orientation and update. - mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null, - mDisplayContent.getDisplayId()); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation()); - appWindow.resizeReported = false; - - // Update the orientation to perform 180 degree rotation and check that resize was reported. - mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE); - sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null, - mDisplayContent.getDisplayId()); - sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); - assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mDisplayContent.getLastOrientation()); - assertTrue(appWindow.resizeReported); - appWindow.removeImmediately(); - } - - @Test - public void testLandscapeSeascapeRotationByPolicy() throws Exception { - // Some plumbing to get the service ready for rotation updates. - sWm.mDisplayReady = true; - sWm.mDisplayEnabled = true; - - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( - TYPE_BASE_APPLICATION); - attrs.setTitle("AppWindow"); - final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); - mToken.addWindow(appWindow); - - // Set initial orientation and update. - performRotation(Surface.ROTATION_90); - appWindow.resizeReported = false; - - // Update the rotation to perform 180 degree rotation and check that resize was reported. - performRotation(Surface.ROTATION_270); - assertTrue(appWindow.resizeReported); - appWindow.removeImmediately(); - } - - private void performRotation(int rotationToReport) { - ((TestWindowManagerPolicy) sWm.mPolicy).rotationToReport = rotationToReport; - sWm.updateRotation(false, false); - // Simulate animator finishing orientation change - sWm.mRoot.mOrientationChangeComplete = true; - sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); - } - - @Test - @Presubmit - public void testGetOrientation() throws Exception { - mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - mToken.setFillsParent(false); - // Can specify orientation if app doesn't fill parent. - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation()); - - mToken.setFillsParent(true); - mToken.setHidden(true); - mToken.sendingToBottom = true; - // Can not specify orientation if app isn't visible even though it fills parent. - assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation()); - // Can specify orientation if the current orientation candidate is orientation behind. - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation(SCREEN_ORIENTATION_BEHIND)); - } - - @Test - @Presubmit - public void testKeyguardFlagsDuringRelaunch() throws Exception { - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( - TYPE_BASE_APPLICATION); - attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD; - attrs.setTitle("AppWindow"); - final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); - - // Add window with show when locked flag - mToken.addWindow(appWindow); - assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow()); - - // Start relaunching - mToken.startRelaunching(); - assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow()); - - // Remove window and make sure that we still report back flag - mToken.removeChild(appWindow); - assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow()); - - // Finish relaunching and ensure flag is now not reported - mToken.finishRelaunching(); - assertFalse(mToken.containsShowWhenLockedWindow() || mToken.containsDismissKeyguardWindow()); - } - - @Test - @FlakyTest(detail = "Promote once confirmed non-flaky") - public void testStuckExitingWindow() throws Exception { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); - closingWindow.mAnimatingExit = true; - closingWindow.mRemoveOnExit = true; - closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, - true /* performLayout */, false /* isVoiceInteraction */); - - // We pretended that we were running an exit animation, but that should have been cleared up - // by changing visibility of AppWindowToken - closingWindow.removeIfPossible(); - assertTrue(closingWindow.mRemoved); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java deleted file mode 100644 index ff631e74e004..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; -import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; -import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; -import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.annotation.UiThreadTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import com.android.server.wm.WindowManagerInternal.AppTransitionListener; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import com.android.server.wm.BoundsAnimationController.BoundsAnimator; - -/** - * Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks - * depending on the various interactions. - * - * We are really concerned about only three of the transition states [F = fullscreen, !F = floating] - * F->!F, !F->!F, and !F->F. Each animation can only be cancelled from the target mid-transition, - * or if a new animation starts on the same target. The tests below verifies that the target is - * notified of all the cases where it is animating and cancelled so that it can respond - * appropriately. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class BoundsAnimationControllerTests extends WindowTestsBase { - - /** - * Mock value animator to simulate updates with. - */ - private class MockValueAnimator extends ValueAnimator { - - private float mFraction; - - public MockValueAnimator getWithValue(float fraction) { - mFraction = fraction; - return this; - } - - @Override - public Object getAnimatedValue() { - return mFraction; - } - } - - /** - * Mock app transition to fire notifications to the bounds animator. - */ - private class MockAppTransition extends AppTransition { - - private AppTransitionListener mListener; - - MockAppTransition(Context context) { - super(context, null); - } - - @Override - void registerListenerLocked(AppTransitionListener listener) { - mListener = listener; - } - - public void notifyTransitionPending() { - mListener.onAppTransitionPendingLocked(); - } - - public void notifyTransitionCancelled(int transit) { - mListener.onAppTransitionCancelledLocked(transit); - } - - public void notifyTransitionStarting(int transit) { - mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0); - } - - public void notifyTransitionFinished() { - mListener.onAppTransitionFinishedLocked(null); - } - } - - /** - * A test animate bounds user to track callbacks from the bounds animation. - */ - private class TestBoundsAnimationTarget implements BoundsAnimationTarget { - - boolean mAwaitingAnimationStart; - boolean mMovedToFullscreen; - boolean mAnimationStarted; - boolean mSchedulePipModeChangedOnStart; - boolean mForcePipModeChangedCallback; - boolean mAnimationEnded; - Rect mAnimationEndFinalStackBounds; - boolean mSchedulePipModeChangedOnEnd; - boolean mBoundsUpdated; - boolean mCancelRequested; - Rect mStackBounds; - Rect mTaskBounds; - - void initialize(Rect from) { - mAwaitingAnimationStart = true; - mMovedToFullscreen = false; - mAnimationStarted = false; - mAnimationEnded = false; - mAnimationEndFinalStackBounds = null; - mForcePipModeChangedCallback = false; - mSchedulePipModeChangedOnStart = false; - mSchedulePipModeChangedOnEnd = false; - mStackBounds = from; - mTaskBounds = null; - mBoundsUpdated = false; - } - - @Override - public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) { - mAwaitingAnimationStart = false; - mAnimationStarted = true; - mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback; - mForcePipModeChangedCallback = forceUpdate; - } - - @Override - public boolean shouldDeferStartOnMoveToFullscreen() { - return true; - } - - @Override - public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) { - // TODO: Once we break the runs apart, we should fail() here if this is called outside - // of onAnimationStart() and onAnimationEnd() - if (mCancelRequested) { - mCancelRequested = false; - return false; - } else { - mBoundsUpdated = true; - mStackBounds = stackBounds; - mTaskBounds = taskBounds; - return true; - } - } - - @Override - public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds, - boolean moveToFullscreen) { - mAnimationEnded = true; - mAnimationEndFinalStackBounds = finalStackBounds; - mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback; - mMovedToFullscreen = moveToFullscreen; - mTaskBounds = null; - } - } - - /** - * Drives the animations, makes common assertions along the way. - */ - private class BoundsAnimationDriver { - - private BoundsAnimationController mController; - private TestBoundsAnimationTarget mTarget; - private BoundsAnimator mAnimator; - - private Rect mFrom; - private Rect mTo; - private Rect mLargerBounds; - private Rect mExpectedFinalBounds; - - BoundsAnimationDriver(BoundsAnimationController controller, - TestBoundsAnimationTarget target) { - mController = controller; - mTarget = target; - } - - BoundsAnimationDriver start(Rect from, Rect to) { - if (mAnimator != null) { - throw new IllegalArgumentException("Call restart() to restart an animation"); - } - - boolean fromFullscreen = from.equals(BOUNDS_FULL); - boolean toFullscreen = to.equals(BOUNDS_FULL); - - mTarget.initialize(from); - - // Started, not running - assertTrue(mTarget.mAwaitingAnimationStart); - assertTrue(!mTarget.mAnimationStarted); - - startImpl(from, to); - - // Ensure that the animator is paused for the all windows drawn signal when animating - // to/from fullscreen - if (fromFullscreen || toFullscreen) { - assertTrue(mAnimator.isPaused()); - mController.onAllWindowsDrawn(); - } else { - assertTrue(!mAnimator.isPaused()); - } - - // Started and running - assertTrue(!mTarget.mAwaitingAnimationStart); - assertTrue(mTarget.mAnimationStarted); - - return this; - } - - BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) { - if (mAnimator == null) { - throw new IllegalArgumentException("Call start() to start a new animation"); - } - - BoundsAnimator oldAnimator = mAnimator; - boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo); - - // Reset the animation start state - mTarget.mAnimationStarted = false; - - // Start animation - startImpl(mTarget.mStackBounds, to); - - if (toSameBounds) { - // Same animator if same final bounds - assertSame(oldAnimator, mAnimator); - } - - if (expectStartedAndPipModeChangedCallback) { - // Replacing animation with pending pip mode changed callback, ensure we update - assertTrue(mTarget.mAnimationStarted); - assertTrue(mTarget.mSchedulePipModeChangedOnStart); - assertTrue(mTarget.mForcePipModeChangedCallback); - } else { - // No animation start for replacing animation - assertTrue(!mTarget.mAnimationStarted); - } - mTarget.mAnimationStarted = true; - return this; - } - - private BoundsAnimationDriver startImpl(Rect from, Rect to) { - boolean fromFullscreen = from.equals(BOUNDS_FULL); - boolean toFullscreen = to.equals(BOUNDS_FULL); - mFrom = new Rect(from); - mTo = new Rect(to); - mExpectedFinalBounds = new Rect(to); - mLargerBounds = getLargerBounds(mFrom, mTo); - - // Start animation - final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen - ? SCHEDULE_PIP_MODE_CHANGED_ON_START - : fromFullscreen - ? SCHEDULE_PIP_MODE_CHANGED_ON_END - : NO_PIP_MODE_CHANGED_CALLBACKS; - mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION, - schedulePipModeChangedState, fromFullscreen, toFullscreen); - - // Original stack bounds, frozen task bounds - assertEquals(mFrom, mTarget.mStackBounds); - assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); - - // Animating to larger size - if (mFrom.equals(mLargerBounds)) { - assertTrue(!mAnimator.animatingToLargerSize()); - } else if (mTo.equals(mLargerBounds)) { - assertTrue(mAnimator.animatingToLargerSize()); - } - - return this; - } - - BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) { - // Callback made - assertTrue(mTarget.mAnimationStarted); - - assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart); - return this; - } - - BoundsAnimationDriver update(float t) { - mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t)); - - // Temporary stack bounds, frozen task bounds - if (t == 0f) { - assertEquals(mFrom, mTarget.mStackBounds); - } else if (t == 1f) { - assertEquals(mTo, mTarget.mStackBounds); - } else { - assertNotEquals(mFrom, mTarget.mStackBounds); - assertNotEquals(mTo, mTarget.mStackBounds); - } - assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); - return this; - } - - BoundsAnimationDriver cancel() { - // Cancel - mTarget.mCancelRequested = true; - mTarget.mBoundsUpdated = false; - mExpectedFinalBounds = null; - - // Update - mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f)); - - // Not started, not running, cancel reset - assertTrue(!mTarget.mCancelRequested); - - // Stack/task bounds not updated - assertTrue(!mTarget.mBoundsUpdated); - - // Callback made - assertTrue(mTarget.mAnimationEnded); - assertNull(mTarget.mAnimationEndFinalStackBounds); - - return this; - } - - BoundsAnimationDriver end() { - mAnimator.end(); - - // Final stack bounds - assertEquals(mTo, mTarget.mStackBounds); - assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds); - assertNull(mTarget.mTaskBounds); - - return this; - } - - BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged, - boolean moveToFullscreen) { - // Callback made - assertTrue(mTarget.mAnimationEnded); - - assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd); - assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen); - return this; - } - - private Rect getLargerBounds(Rect r1, Rect r2) { - int r1Area = r1.width() * r1.height(); - int r2Area = r2.width() * r2.height(); - if (r1Area <= r2Area) { - return r2; - } else { - return r1; - } - } - } - - // Constants - private static final boolean SCHEDULE_PIP_MODE_CHANGED = true; - private static final boolean MOVE_TO_FULLSCREEN = true; - private static final int DURATION = 100; - - // Some dummy bounds to represent fullscreen and floating bounds - private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100); - private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95); - private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95); - - // Common - private MockAppTransition mMockAppTransition; - private MockValueAnimator mMockAnimator; - private TestBoundsAnimationTarget mTarget; - private BoundsAnimationController mController; - private BoundsAnimationDriver mDriver; - - // Temp - private Rect mTmpRect = new Rect(); - - @Override - public void setUp() throws Exception { - super.setUp(); - - final Context context = InstrumentationRegistry.getTargetContext(); - final Handler handler = new Handler(Looper.getMainLooper()); - mMockAppTransition = new MockAppTransition(context); - mMockAnimator = new MockValueAnimator(); - mTarget = new TestBoundsAnimationTarget(); - mController = new BoundsAnimationController(context, mMockAppTransition, handler, null); - mDriver = new BoundsAnimationDriver(mController, mTarget); - } - - /** BASE TRANSITIONS **/ - - @UiThreadTest - @Test - public void testFullscreenToFloatingTransition() throws Exception { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0f) - .update(0.5f) - .update(1f) - .end() - .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFloatingToFullscreenTransition() throws Exception { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) - .expectStarted(SCHEDULE_PIP_MODE_CHANGED) - .update(0f) - .update(0.5f) - .update(1f) - .end() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFloatingToSmallerFloatingTransition() throws Exception { - mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0f) - .update(0.5f) - .update(1f) - .end() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFloatingToLargerFloatingTransition() throws Exception { - mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0f) - .update(0.5f) - .update(1f) - .end() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - /** F->!F w/ CANCEL **/ - - @UiThreadTest - @Test - public void testFullscreenToFloatingCancelFromTarget() throws Exception { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .cancel() - .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */) - .end() - .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .restart(BOUNDS_SMALLER_FLOATING, - false /* expectStartedAndPipModeChangedCallback */) - .end() - .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception { - // When animating from fullscreen and the animation is interruped, we expect the animation - // start callback to be made, with a forced pip mode change callback - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */) - .end() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); - } - - /** !F->F w/ CANCEL **/ - - @UiThreadTest - @Test - public void testFloatingToFullscreenCancelFromTarget() throws Exception { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) - .expectStarted(SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .cancel() - .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) - .expectStarted(SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */) - .end() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) - .expectStarted(SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .restart(BOUNDS_SMALLER_FLOATING, - false /* expectStartedAndPipModeChangedCallback */) - .end() - .expectEnded(SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); - } - - /** !F->!F w/ CANCEL **/ - - @UiThreadTest - @Test - public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception { - mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .cancel() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - @UiThreadTest - @Test - public void testFloatingToLargerFloatingCancelFromTarget() throws Exception { - mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) - .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) - .update(0.25f) - .cancel() - .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); - } - - /** MISC **/ - - @UiThreadTest - @Test - public void testBoundsAreCopied() throws Exception { - Rect from = new Rect(0, 0, 100, 100); - Rect to = new Rect(25, 25, 75, 75); - mDriver.start(from, to) - .update(0.25f) - .end(); - assertEquals(new Rect(0, 0, 100, 100), from); - assertEquals(new Rect(25, 25, 75, 75), to); - } - - /** - * @return whether the task and stack bounds would be the same if they were at the same offset. - */ - private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) { - mTmpRect.set(taskBounds); - mTmpRect.offsetTo(stackBounds.left, stackBounds.top); - return stackBounds.equals(mTmpRect); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java deleted file mode 100644 index 192e1564ae8d..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/ConfigurationContainerTests.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; -import static android.content.res.Configuration.EMPTY; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.content.res.Configuration; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -/** - * Test class for {@link ConfigurationContainer}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.ConfigurationContainerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ConfigurationContainerTests { - - @Test - public void testConfigurationInit() throws Exception { - // Check root container initial config. - final TestConfigurationContainer root = new TestConfigurationContainer(); - assertEquals(EMPTY, root.getOverrideConfiguration()); - assertEquals(EMPTY, root.getMergedOverrideConfiguration()); - assertEquals(EMPTY, root.getConfiguration()); - - // Check child initial config. - final TestConfigurationContainer child1 = root.addChild(); - assertEquals(EMPTY, child1.getOverrideConfiguration()); - assertEquals(EMPTY, child1.getMergedOverrideConfiguration()); - assertEquals(EMPTY, child1.getConfiguration()); - - // Check child initial config if root has overrides. - final Configuration rootOverrideConfig = new Configuration(); - rootOverrideConfig.fontScale = 1.3f; - root.onOverrideConfigurationChanged(rootOverrideConfig); - final TestConfigurationContainer child2 = root.addChild(); - assertEquals(EMPTY, child2.getOverrideConfiguration()); - assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration()); - assertEquals(rootOverrideConfig, child2.getConfiguration()); - - // Check child initial config if root has parent config set. - final Configuration rootParentConfig = new Configuration(); - rootParentConfig.fontScale = 0.8f; - rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE; - root.onConfigurationChanged(rootParentConfig); - final Configuration rootFullConfig = new Configuration(rootParentConfig); - rootFullConfig.updateFrom(rootOverrideConfig); - - final TestConfigurationContainer child3 = root.addChild(); - assertEquals(EMPTY, child3.getOverrideConfiguration()); - assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration()); - assertEquals(rootFullConfig, child3.getConfiguration()); - } - - @Test - public void testConfigurationChangeOnAddRemove() throws Exception { - // Init root's config. - final TestConfigurationContainer root = new TestConfigurationContainer(); - final Configuration rootOverrideConfig = new Configuration(); - rootOverrideConfig.fontScale = 1.3f; - root.onOverrideConfigurationChanged(rootOverrideConfig); - - // Init child's config. - final TestConfigurationContainer child = root.addChild(); - final Configuration childOverrideConfig = new Configuration(); - childOverrideConfig.densityDpi = 320; - child.onOverrideConfigurationChanged(childOverrideConfig); - final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration()); - mergedOverrideConfig.updateFrom(childOverrideConfig); - - // Check configuration update when child is removed from parent. - root.removeChild(child); - assertEquals(childOverrideConfig, child.getOverrideConfiguration()); - assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration()); - assertEquals(mergedOverrideConfig, child.getConfiguration()); - - // It may be paranoia... but let's check if parent's config didn't change after removal. - assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); - assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); - assertEquals(rootOverrideConfig, root.getConfiguration()); - - // Init different root - final TestConfigurationContainer root2 = new TestConfigurationContainer(); - final Configuration rootOverrideConfig2 = new Configuration(); - rootOverrideConfig2.fontScale = 1.1f; - root2.onOverrideConfigurationChanged(rootOverrideConfig2); - - // Check configuration update when child is added to different parent. - mergedOverrideConfig.setTo(rootOverrideConfig2); - mergedOverrideConfig.updateFrom(childOverrideConfig); - root2.addChild(child); - assertEquals(childOverrideConfig, child.getOverrideConfiguration()); - assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration()); - assertEquals(mergedOverrideConfig, child.getConfiguration()); - } - - @Test - public void testConfigurationChangePropagation() throws Exception { - // Builds 3-level vertical hierarchy with one configuration container on each level. - // In addition to different overrides on each level, everyone in hierarchy will have one - // common overridden value - orientation; - - // Init root's config. - final TestConfigurationContainer root = new TestConfigurationContainer(); - final Configuration rootOverrideConfig = new Configuration(); - rootOverrideConfig.fontScale = 1.3f; - rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - root.onOverrideConfigurationChanged(rootOverrideConfig); - - // Init children. - final TestConfigurationContainer child1 = root.addChild(); - final Configuration childOverrideConfig1 = new Configuration(); - childOverrideConfig1.densityDpi = 320; - childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE; - child1.onOverrideConfigurationChanged(childOverrideConfig1); - - final TestConfigurationContainer child2 = child1.addChild(); - final Configuration childOverrideConfig2 = new Configuration(); - childOverrideConfig2.screenWidthDp = 150; - childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT; - child2.onOverrideConfigurationChanged(childOverrideConfig2); - - // Check configuration on all levels when root override is updated. - rootOverrideConfig.smallestScreenWidthDp = 200; - root.onOverrideConfigurationChanged(rootOverrideConfig); - - final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig); - mergedOverrideConfig1.updateFrom(childOverrideConfig1); - final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1); - - final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1); - mergedOverrideConfig2.updateFrom(childOverrideConfig2); - final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2); - - assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); - assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); - assertEquals(rootOverrideConfig, root.getConfiguration()); - - assertEquals(childOverrideConfig1, child1.getOverrideConfiguration()); - assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration()); - assertEquals(mergedConfig1, child1.getConfiguration()); - - assertEquals(childOverrideConfig2, child2.getOverrideConfiguration()); - assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration()); - assertEquals(mergedConfig2, child2.getConfiguration()); - - // Check configuration on all levels when root parent config is updated. - final Configuration rootParentConfig = new Configuration(); - rootParentConfig.screenHeightDp = 100; - rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT; - root.onConfigurationChanged(rootParentConfig); - final Configuration mergedRootConfig = new Configuration(rootParentConfig); - mergedRootConfig.updateFrom(rootOverrideConfig); - - mergedConfig1.setTo(mergedRootConfig); - mergedConfig1.updateFrom(mergedOverrideConfig1); - - mergedConfig2.setTo(mergedConfig1); - mergedConfig2.updateFrom(mergedOverrideConfig2); - - assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); - assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); - assertEquals(mergedRootConfig, root.getConfiguration()); - - assertEquals(childOverrideConfig1, child1.getOverrideConfiguration()); - assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration()); - assertEquals(mergedConfig1, child1.getConfiguration()); - - assertEquals(childOverrideConfig2, child2.getOverrideConfiguration()); - assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration()); - assertEquals(mergedConfig2, child2.getConfiguration()); - } - - @Test - public void testSetWindowingMode() throws Exception { - final TestConfigurationContainer root = new TestConfigurationContainer(); - root.setWindowingMode(WINDOWING_MODE_UNDEFINED); - final TestConfigurationContainer child = root.addChild(); - child.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(WINDOWING_MODE_UNDEFINED, root.getWindowingMode()); - assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode()); - - root.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertEquals(WINDOWING_MODE_FULLSCREEN, root.getWindowingMode()); - assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode()); - } - - @Test - public void testSetActivityType() throws Exception { - final TestConfigurationContainer root = new TestConfigurationContainer(); - root.setActivityType(ACTIVITY_TYPE_UNDEFINED); - final TestConfigurationContainer child = root.addChild(); - child.setActivityType(ACTIVITY_TYPE_STANDARD); - assertEquals(ACTIVITY_TYPE_UNDEFINED, root.getActivityType()); - assertEquals(ACTIVITY_TYPE_STANDARD, child.getActivityType()); - - boolean gotException = false; - try { - // Can't change activity type once set. - child.setActivityType(ACTIVITY_TYPE_HOME); - } catch (IllegalStateException e) { - gotException = true; - } - assertTrue("Can't change activity type once set.", gotException); - - // TODO: Commenting out for now until we figure-out a good way to test these rules that - // should only apply to system process. - /* - gotException = false; - try { - // Parent can't change child's activity type once set. - root.setActivityType(ACTIVITY_TYPE_HOME); - } catch (IllegalStateException e) { - gotException = true; - } - assertTrue("Parent can't change activity type once set.", gotException); - assertEquals(ACTIVITY_TYPE_HOME, root.getActivityType()); - - final TestConfigurationContainer child2 = new TestConfigurationContainer(); - child2.setActivityType(ACTIVITY_TYPE_RECENTS); - - gotException = false; - try { - // Can't re-parent to a different activity type. - root.addChild(child2); - } catch (IllegalStateException e) { - gotException = true; - } - assertTrue("Can't re-parent to a different activity type.", gotException); - */ - - } - - @Test - public void testRegisterConfigurationChangeListener() throws Exception { - final TestConfigurationContainer container = new TestConfigurationContainer(); - final TestConfigurationChangeListener listener = new TestConfigurationChangeListener(); - final Configuration config = new Configuration(); - config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - config.windowConfiguration.setAppBounds(10, 10, 10, 10); - container.onOverrideConfigurationChanged(config); - container.registerConfigurationChangeListener(listener); - // Assert listener got the current config. of the container after it was registered. - assertEquals(config, listener.mOverrideConfiguration); - // Assert listener gets changes to override configuration. - container.onOverrideConfigurationChanged(EMPTY); - assertEquals(EMPTY, listener.mOverrideConfiguration); - } - - /** - * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed - * for testing. - */ - private class TestConfigurationContainer - extends ConfigurationContainer { - private List mChildren = new ArrayList<>(); - private TestConfigurationContainer mParent; - - TestConfigurationContainer addChild(TestConfigurationContainer childContainer) { - childContainer.mParent = this; - childContainer.onParentChanged(); - mChildren.add(childContainer); - return childContainer; - } - - TestConfigurationContainer addChild() { - return addChild(new TestConfigurationContainer()); - } - - void removeChild(TestConfigurationContainer child) { - child.mParent = null; - child.onParentChanged(); - } - - @Override - protected int getChildCount() { - return mChildren.size(); - } - - @Override - protected TestConfigurationContainer getChildAt(int index) { - return mChildren.get(index); - } - - @Override - protected ConfigurationContainer getParent() { - return mParent; - } - } - - private class TestConfigurationChangeListener implements ConfigurationContainerListener { - - final Configuration mOverrideConfiguration = new Configuration(); - - public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { - mOverrideConfiguration.setTo(overrideConfiguration); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java deleted file mode 100644 index 6769e40dca78..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.runner.AndroidJUnit4; -import android.view.SurfaceControl; -import android.view.SurfaceSession; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.wm.DimmerTests; - */ -@Presubmit -@RunWith(AndroidJUnit4.class) -public class DimmerTests extends WindowTestsBase { - - private class TestWindowContainer extends WindowContainer { - final SurfaceControl mControl = mock(SurfaceControl.class); - final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class); - - TestWindowContainer() { - super(sWm); - } - - @Override - public SurfaceControl getSurfaceControl() { - return mControl; - } - - @Override - public SurfaceControl.Transaction getPendingTransaction() { - return mTransaction; - } - } - - private class MockSurfaceBuildingContainer extends WindowContainer { - final SurfaceSession mSession = new SurfaceSession(); - final SurfaceControl mHostControl = mock(SurfaceControl.class); - final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class); - - MockSurfaceBuildingContainer() { - super(sWm); - } - - class MockSurfaceBuilder extends SurfaceControl.Builder { - MockSurfaceBuilder(SurfaceSession ss) { - super(ss); - } - - @Override - public SurfaceControl build() { - return mock(SurfaceControl.class); - } - } - - @Override - SurfaceControl.Builder makeChildSurface(WindowContainer child) { - return new MockSurfaceBuilder(mSession); - } - - @Override - public SurfaceControl getSurfaceControl() { - return mHostControl; - } - - @Override - public SurfaceControl.Transaction getPendingTransaction() { - return mHostTransaction; - } - } - - private MockSurfaceBuildingContainer mHost; - private Dimmer mDimmer; - private SurfaceControl.Transaction mTransaction; - private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter; - - private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { - @Override - public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden) { - surfaceAnimator.mAnimationFinishedCallback.run(); - } - } - - @Before - public void setUp() throws Exception { - super.setUp(); - mHost = new MockSurfaceBuildingContainer(); - mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); - mTransaction = mock(SurfaceControl.Transaction.class); - mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter); - } - - @Test - public void testDimAboveNoChildCreatesSurface() throws Exception { - final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, alpha); - - SurfaceControl dimLayer = getDimLayer(); - - assertNotNull("Dimmer should have created a surface", dimLayer); - - verify(mTransaction).setAlpha(dimLayer, alpha); - verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE); - } - - @Test - public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception { - float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, alpha); - final SurfaceControl firstSurface = getDimLayer(); - - alpha = 0.9f; - mDimmer.dimAbove(mTransaction, alpha); - - assertEquals(firstSurface, getDimLayer()); - verify(mTransaction).setAlpha(firstSurface, 0.9f); - } - - @Test - public void testUpdateDimsAppliesSize() throws Exception { - mDimmer.dimAbove(mTransaction, 0.8f); - - int width = 100; - int height = 300; - Rect bounds = new Rect(0, 0, width, height); - mDimmer.updateDims(mTransaction, bounds); - - verify(mTransaction).setSize(getDimLayer(), width, height); - verify(mTransaction).show(getDimLayer()); - } - - @Test - public void testDimAboveNoChildNotReset() throws Exception { - mDimmer.dimAbove(mTransaction, 0.8f); - SurfaceControl dimLayer = getDimLayer(); - mDimmer.resetDimStates(); - - mDimmer.updateDims(mTransaction, new Rect()); - verify(mTransaction).show(getDimLayer()); - verify(dimLayer, never()).destroy(); - } - - @Test - public void testDimAboveWithChildCreatesSurfaceAboveChild() throws Exception { - TestWindowContainer child = new TestWindowContainer(); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); - SurfaceControl dimLayer = getDimLayer(); - - assertNotNull("Dimmer should have created a surface", dimLayer); - - verify(mTransaction).setAlpha(dimLayer, alpha); - verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, 1); - } - - @Test - public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() throws Exception { - TestWindowContainer child = new TestWindowContainer(); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimBelow(mTransaction, child, alpha); - SurfaceControl dimLayer = getDimLayer(); - - assertNotNull("Dimmer should have created a surface", dimLayer); - - verify(mTransaction).setAlpha(dimLayer, alpha); - verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, -1); - } - - @Test - public void testDimBelowWithChildSurfaceDestroyedWhenReset() throws Exception { - TestWindowContainer child = new TestWindowContainer(); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); - SurfaceControl dimLayer = getDimLayer(); - mDimmer.resetDimStates(); - - mDimmer.updateDims(mTransaction, new Rect()); - verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( - SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean()); - verify(dimLayer).destroy(); - } - - @Test - public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() throws Exception { - TestWindowContainer child = new TestWindowContainer(); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); - SurfaceControl dimLayer = getDimLayer(); - mDimmer.resetDimStates(); - mDimmer.dimAbove(mTransaction, child, alpha); - - mDimmer.updateDims(mTransaction, new Rect()); - verify(mTransaction).show(dimLayer); - verify(dimLayer, never()).destroy(); - } - - @Test - public void testDimUpdateWhileDimming() throws Exception { - Rect bounds = new Rect(); - TestWindowContainer child = new TestWindowContainer(); - mHost.addChild(child, 0); - - final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, child, alpha); - - SurfaceControl dimLayer = getDimLayer(); - bounds.set(0, 0, 10, 10); - mDimmer.updateDims(mTransaction, bounds); - verify(mTransaction, times(1)).show(dimLayer); - verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height()); - verify(mTransaction).setPosition(dimLayer, 0, 0); - - bounds.set(10, 10, 30, 30); - mDimmer.updateDims(mTransaction, bounds); - verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height()); - verify(mTransaction).setPosition(dimLayer, 10, 10); - } - - @Test - public void testRemoveDimImmediately() throws Exception { - TestWindowContainer child = new TestWindowContainer(); - mHost.addChild(child, 0); - - mDimmer.dimAbove(mTransaction, child, 1); - SurfaceControl dimLayer = getDimLayer(); - mDimmer.updateDims(mTransaction, new Rect()); - verify(mTransaction, times(1)).show(dimLayer); - - reset(mSurfaceAnimatorStarter); - mDimmer.dontAnimateExit(); - mDimmer.resetDimStates(); - mDimmer.updateDims(mTransaction, new Rect()); - verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( - SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean()); - verify(mTransaction).destroy(dimLayer); - } - - private SurfaceControl getDimLayer() { - return mDimmer.mDimState.mDimLayer; - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java deleted file mode 100644 index ac196f9c80dc..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.DisplayCutout.fromBoundingRect; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; -import static com.android.server.wm.WindowContainer.POSITION_TOP; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.annotation.SuppressLint; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.SystemClock; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.DisplayMetrics; -import android.util.SparseIntArray; -import android.view.DisplayCutout; -import android.view.MotionEvent; -import android.view.Surface; - -import com.android.server.wm.utils.WmDisplayCutout; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * Tests for the {@link DisplayContent} class. - * - * Build/Install/Run: - * atest com.android.server.wm.DisplayContentTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class DisplayContentTests extends WindowTestsBase { - - @Test - @FlakyTest(bugId = 77772044) - public void testForAllWindows() throws Exception { - final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, - mDisplayContent, "exiting app"); - final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken; - exitingAppToken.mIsExiting = true; - exitingAppToken.getTask().mStack.mExitingAppTokens.add(exitingAppToken); - - assertForAllWindowsOrder(Arrays.asList( - mWallpaperWindow, - exitingAppWindow, - mChildAppWindowBelow, - mAppWindow, - mChildAppWindowAbove, - mDockedDividerWindow, - mStatusBarWindow, - mNavBarWindow, - mImeWindow, - mImeDialogWindow)); - } - - @Test - public void testForAllWindows_WithAppImeTarget() throws Exception { - final WindowState imeAppTarget = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); - - sWm.mInputMethodTarget = imeAppTarget; - - assertForAllWindowsOrder(Arrays.asList( - mWallpaperWindow, - mChildAppWindowBelow, - mAppWindow, - mChildAppWindowAbove, - imeAppTarget, - mImeWindow, - mImeDialogWindow, - mDockedDividerWindow, - mStatusBarWindow, - mNavBarWindow)); - } - - @Test - public void testForAllWindows_WithChildWindowImeTarget() throws Exception { - sWm.mInputMethodTarget = mChildAppWindowAbove; - - assertForAllWindowsOrder(Arrays.asList( - mWallpaperWindow, - mChildAppWindowBelow, - mAppWindow, - mChildAppWindowAbove, - mImeWindow, - mImeDialogWindow, - mDockedDividerWindow, - mStatusBarWindow, - mNavBarWindow)); - } - - @Test - public void testForAllWindows_WithStatusBarImeTarget() throws Exception { - sWm.mInputMethodTarget = mStatusBarWindow; - - assertForAllWindowsOrder(Arrays.asList( - mWallpaperWindow, - mChildAppWindowBelow, - mAppWindow, - mChildAppWindowAbove, - mDockedDividerWindow, - mStatusBarWindow, - mImeWindow, - mImeDialogWindow, - mNavBarWindow)); - } - - @Test - public void testForAllWindows_WithInBetweenWindowToken() throws Exception { - // This window is set-up to be z-ordered between some windows that go in the same token like - // the nav bar and status bar. - final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION, - mDisplayContent, "voiceInteractionWindow"); - - assertForAllWindowsOrder(Arrays.asList( - mWallpaperWindow, - mChildAppWindowBelow, - mAppWindow, - mChildAppWindowAbove, - mDockedDividerWindow, - voiceInteractionWindow, - mStatusBarWindow, - mNavBarWindow, - mImeWindow, - mImeDialogWindow)); - } - - @Test - public void testComputeImeTarget() throws Exception { - // Verify that an app window can be an ime target. - final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); - appWin.setHasSurface(true); - assertTrue(appWin.canBeImeTarget()); - WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); - assertEquals(appWin, imeTarget); - appWin.mHidden = false; - - // Verify that an child window can be an ime target. - final WindowState childWin = createWindow(appWin, - TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); - childWin.setHasSurface(true); - assertTrue(childWin.canBeImeTarget()); - imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); - assertEquals(childWin, imeTarget); - } - - /** - * This tests stack movement between displays and proper stack's, task's and app token's display - * container references updates. - */ - @Test - public void testMoveStackBetweenDisplays() throws Exception { - // Create a second display. - final DisplayContent dc = createNewDisplay(); - - // Add stack with activity. - final TaskStack stack = createTaskStackOnDisplay(dc); - assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId()); - assertEquals(dc, stack.getParent().getParent()); - assertEquals(dc, stack.getDisplayContent()); - - final Task task = createTaskInStack(stack, 0 /* userId */); - final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken( - dc); - task.addChild(token, 0); - assertEquals(dc, task.getDisplayContent()); - assertEquals(dc, token.getDisplayContent()); - - // Move stack to first display. - mDisplayContent.moveStackToDisplay(stack, true /* onTop */); - assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); - assertEquals(mDisplayContent, stack.getParent().getParent()); - assertEquals(mDisplayContent, stack.getDisplayContent()); - assertEquals(mDisplayContent, task.getDisplayContent()); - assertEquals(mDisplayContent, token.getDisplayContent()); - } - - /** - * This tests override configuration updates for display content. - */ - @Test - public void testDisplayOverrideConfigUpdate() throws Exception { - final int displayId = mDisplayContent.getDisplayId(); - final Configuration currentOverrideConfig = mDisplayContent.getOverrideConfiguration(); - - // Create new, slightly changed override configuration and apply it to the display. - final Configuration newOverrideConfig = new Configuration(currentOverrideConfig); - newOverrideConfig.densityDpi += 120; - newOverrideConfig.fontScale += 0.3; - - sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, displayId); - - // Check that override config is applied. - assertEquals(newOverrideConfig, mDisplayContent.getOverrideConfiguration()); - } - - /** - * This tests global configuration updates when default display config is updated. - */ - @Test - public void testDefaultDisplayOverrideConfigUpdate() throws Exception { - final Configuration currentConfig = mDisplayContent.getConfiguration(); - - // Create new, slightly changed override configuration and apply it to the display. - final Configuration newOverrideConfig = new Configuration(currentConfig); - newOverrideConfig.densityDpi += 120; - newOverrideConfig.fontScale += 0.3; - - sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, DEFAULT_DISPLAY); - - // Check that global configuration is updated, as we've updated default display's config. - Configuration globalConfig = sWm.mRoot.getConfiguration(); - assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi); - assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); - - // Return back to original values. - sWm.setNewDisplayOverrideConfiguration(currentConfig, DEFAULT_DISPLAY); - globalConfig = sWm.mRoot.getConfiguration(); - assertEquals(currentConfig.densityDpi, globalConfig.densityDpi); - assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); - } - - /** - * Tests tapping on a stack in different display results in window gaining focus. - */ - @Test - public void testInputEventBringsCorrectDisplayInFocus() throws Exception { - DisplayContent dc0 = sWm.getDefaultDisplayContentLocked(); - // Create a second display - final DisplayContent dc1 = createNewDisplay(); - - // Add stack with activity. - final TaskStack stack0 = createTaskStackOnDisplay(dc0); - final Task task0 = createTaskInStack(stack0, 0 /* userId */); - final WindowTestUtils.TestAppWindowToken token = - WindowTestUtils.createTestAppWindowToken(dc0); - task0.addChild(token, 0); - dc0.mTapDetector = new TaskTapPointerEventListener(sWm, dc0); - sWm.registerPointerEventListener(dc0.mTapDetector); - final TaskStack stack1 = createTaskStackOnDisplay(dc1); - final Task task1 = createTaskInStack(stack1, 0 /* userId */); - final WindowTestUtils.TestAppWindowToken token1 = - WindowTestUtils.createTestAppWindowToken(dc0); - task1.addChild(token1, 0); - dc1.mTapDetector = new TaskTapPointerEventListener(sWm, dc0); - sWm.registerPointerEventListener(dc1.mTapDetector); - - // tap on primary display (by sending ACTION_DOWN followed by ACTION_UP) - DisplayMetrics dm0 = dc0.getDisplayMetrics(); - dc0.mTapDetector.onPointerEvent( - createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, true)); - dc0.mTapDetector.onPointerEvent( - createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false)); - - // Check focus is on primary display. - assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow()); - - // Tap on secondary display - DisplayMetrics dm1 = dc1.getDisplayMetrics(); - dc1.mTapDetector.onPointerEvent( - createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, true)); - dc1.mTapDetector.onPointerEvent( - createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false)); - - // Check focus is on secondary. - assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow()); - } - - @Test - public void testFocusedWindowMultipleDisplays() throws Exception { - // Create a focusable window and check that focus is calculated correctly - final WindowState window1 = - createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); - assertEquals(window1, sWm.mRoot.computeFocusedWindow()); - - // Check that a new display doesn't affect focus - final DisplayContent dc = createNewDisplay(); - assertEquals(window1, sWm.mRoot.computeFocusedWindow()); - - // Add a window to the second display, and it should be focused - final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2"); - assertEquals(window2, sWm.mRoot.computeFocusedWindow()); - - // Move the first window to the to including parents, and make sure focus is updated - window1.getParent().positionChildAt(POSITION_TOP, window1, true); - assertEquals(window1, sWm.mRoot.computeFocusedWindow()); - } - - @Test - public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { - final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, - sWm.getDefaultDisplayContentLocked(), "keyguard"); - assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); - - // Add a window to a second display, and it should be focused - final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); - assertEquals(win, sWm.mRoot.computeFocusedWindow()); - - mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true; - assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); - } - - /** - * This tests setting the maximum ui width on a display. - */ - @Test - public void testMaxUiWidth() throws Exception { - final int baseWidth = 1440; - final int baseHeight = 2560; - final int baseDensity = 300; - - mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); - - final int maxWidth = 300; - final int resultingHeight = (maxWidth * baseHeight) / baseWidth; - final int resultingDensity = (maxWidth * baseDensity) / baseWidth; - - mDisplayContent.setMaxUiWidth(maxWidth); - verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity); - - // Assert setting values again does not change; - mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); - verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity); - - final int smallerWidth = 200; - final int smallerHeight = 400; - final int smallerDensity = 100; - - // Specify smaller dimension, verify that it is honored - mDisplayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity); - verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity); - - // Verify that setting the max width to a greater value than the base width has no effect - mDisplayContent.setMaxUiWidth(maxWidth); - verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity); - } - - /** - * This test enforces that the pinned stack is always kept as the top stack. - */ - @Test - public void testPinnedStackLocation() { - final TaskStack pinnedStack = createStackControllerOnStackOnDisplay( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; - // Ensure that the pinned stack is the top stack - assertEquals(pinnedStack, mDisplayContent.getPinnedStack()); - assertEquals(pinnedStack, mDisplayContent.getTopStack()); - // By default, this should try to create a new stack on top - final TaskStack otherStack = createTaskStackOnDisplay(mDisplayContent); - // Ensure that the other stack is on the display. - assertEquals(mDisplayContent, otherStack.getDisplayContent()); - // Ensure that the pinned stack is still on top - assertEquals(pinnedStack, mDisplayContent.getTopStack()); - } - - /** - * Test that WM does not report displays to AM that are pending to be removed. - */ - @Test - public void testDontReportDeferredRemoval() { - // Create a display and add an animating window to it. - final DisplayContent dc = createNewDisplay(); - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - window.mAnimatingExit = true; - // Request display removal, it should be deferred. - dc.removeIfPossible(); - // Request ordered display ids from WM. - final SparseIntArray orderedDisplayIds = new SparseIntArray(); - sWm.getDisplaysInFocusOrder(orderedDisplayIds); - // Make sure that display that is marked for removal is not reported. - assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId())); - } - - @Test - public void testDisplayCutout_rot0() throws Exception { - synchronized (sWm.getWindowManagerLock()) { - final DisplayContent dc = createNewDisplay(); - dc.mInitialDisplayWidth = 200; - dc.mInitialDisplayHeight = 400; - Rect r = new Rect(80, 0, 120, 10); - final DisplayCutout cutout = new WmDisplayCutout( - fromBoundingRect(r.left, r.top, r.right, r.bottom), null) - .computeSafeInsets(200, 400).getDisplayCutout(); - - dc.mInitialDisplayCutout = cutout; - dc.setRotation(Surface.ROTATION_0); - dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. - - assertEquals(cutout, dc.getDisplayInfo().displayCutout); - } - } - - @Test - public void testDisplayCutout_rot90() throws Exception { - synchronized (sWm.getWindowManagerLock()) { - final DisplayContent dc = createNewDisplay(); - dc.mInitialDisplayWidth = 200; - dc.mInitialDisplayHeight = 400; - Rect r1 = new Rect(80, 0, 120, 10); - final DisplayCutout cutout = new WmDisplayCutout( - fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null) - .computeSafeInsets(200, 400).getDisplayCutout(); - - dc.mInitialDisplayCutout = cutout; - dc.setRotation(Surface.ROTATION_90); - dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. - - final Rect r = new Rect(0, 80, 10, 120); - assertEquals(new WmDisplayCutout( - fromBoundingRect(r.left, r.top, r.right, r.bottom), null) - .computeSafeInsets(400, 200).getDisplayCutout(), dc.getDisplayInfo().displayCutout); - } - } - - @Test - public void testLayoutSeq_assignedDuringLayout() throws Exception { - synchronized (sWm.getWindowManagerLock()) { - - final DisplayContent dc = createNewDisplay(); - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - - dc.setLayoutNeeded(); - dc.performLayout(true /* initial */, false /* updateImeWindows */); - - assertThat(win.mLayoutSeq, is(dc.mLayoutSeq)); - } - } - - @Test - @SuppressLint("InlinedApi") - public void testOrientationDefinedByKeyguard() { - final DisplayContent dc = createNewDisplay(); - // Create a window that requests landscape orientation. It will define device orientation - // by default. - final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - window.mAppToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, dc, "keyguard"); - keyguard.mHasSurface = true; - keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - - assertEquals("Screen orientation must be defined by the app window by default", - SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); - - keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; - assertEquals("Visible keyguard must influence device orientation", - SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation()); - - sWm.setKeyguardGoingAway(true); - assertEquals("Keyguard that is going away must not influence device orientation", - SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); - } - - @Test - public void testDisableDisplayInfoOverrideFromWindowManager() { - final DisplayContent dc = createNewDisplay(); - - assertTrue(dc.mShouldOverrideDisplayConfiguration); - sWm.dontOverrideDisplayInfo(dc.getDisplayId()); - - assertFalse(dc.mShouldOverrideDisplayConfiguration); - verify(sWm.mDisplayManagerInternal, times(1)) - .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null); - } - - private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth, - int expectedBaseHeight, int expectedBaseDensity) { - assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth); - assertEquals(displayContent.mBaseDisplayHeight, expectedBaseHeight); - assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity); - } - - private void assertForAllWindowsOrder(List expectedWindowsBottomToTop) { - final LinkedList actualWindows = new LinkedList<>(); - - // Test forward traversal. - mDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */); - assertThat("bottomToTop", actualWindows, is(expectedWindowsBottomToTop)); - - actualWindows.clear(); - - // Test backward traversal. - mDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */); - assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop))); - } - - private static List reverseList(List list) { - final ArrayList result = new ArrayList<>(list); - Collections.reverse(result); - return result; - } - - private MotionEvent createTapEvent(float x, float y, boolean isDownEvent) { - final long downTime = SystemClock.uptimeMillis(); - final long eventTime = SystemClock.uptimeMillis() + 100; - final int metaState = 0; - - return MotionEvent.obtain( - downTime, - eventTime, - isDownEvent ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, - x, - y, - metaState); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java deleted file mode 100644 index a09656c7dc92..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.ClipData; -import android.graphics.PixelFormat; -import android.os.IBinder; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManagerInternal; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.InputChannel; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.view.View; -import com.android.internal.annotations.GuardedBy; -import com.android.server.LocalServices; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link DragDropController} class. - * - * atest com.android.server.wm.DragDropControllerTests - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -@Presubmit -public class DragDropControllerTests extends WindowTestsBase { - private static final int TIMEOUT_MS = 3000; - private TestDragDropController mTarget; - private WindowState mWindow; - private IBinder mToken; - - static class TestDragDropController extends DragDropController { - @GuardedBy("sWm.mWindowMap") - private Runnable mCloseCallback; - - TestDragDropController(WindowManagerService service, Looper looper) { - super(service, looper); - } - - void setOnClosedCallbackLocked(Runnable runnable) { - assertTrue(dragDropActiveLocked()); - mCloseCallback = runnable; - } - - @Override - void onDragStateClosedLocked(DragState dragState) { - super.onDragStateClosedLocked(dragState); - if (mCloseCallback != null) { - mCloseCallback.run(); - mCloseCallback = null; - } - } - } - - /** - * Creates a window state which can be used as a drop target. - */ - private WindowState createDropTargetWindow(String name, int ownerId) { - final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken( - mDisplayContent); - final TaskStack stack = createStackControllerOnStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; - final Task task = createTaskInStack(stack, ownerId); - task.addChild(token, 0); - - final WindowState window = createWindow( - null, TYPE_BASE_APPLICATION, token, name, ownerId, false); - window.mInputChannel = new InputChannel(); - window.mHasSurface = true; - return window; - } - - @Before - public void setUp() throws Exception { - final UserManagerInternal userManager = mock(UserManagerInternal.class); - LocalServices.addService(UserManagerInternal.class, userManager); - - super.setUp(); - - mTarget = new TestDragDropController(sWm, sWm.mH.getLooper()); - mDisplayContent = spy(mDisplayContent); - mWindow = createDropTargetWindow("Drag test window", 0); - when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow); - when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); - - synchronized (sWm.mWindowMap) { - sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); - } - } - - @After - public void tearDown() throws Exception { - LocalServices.removeServiceForTest(UserManagerInternal.class); - final CountDownLatch latch; - synchronized (sWm.mWindowMap) { - if (!mTarget.dragDropActiveLocked()) { - return; - } - if (mToken != null) { - mTarget.cancelDragAndDrop(mToken); - } - latch = new CountDownLatch(1); - mTarget.setOnClosedCallbackLocked(() -> { - latch.countDown(); - }); - } - assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - } - - @Test - public void testDragFlow() throws Exception { - dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0); - } - - @Test - public void testPerformDrag_NullDataWithGrantUri() throws Exception { - dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); - } - - @Test - public void testPerformDrag_NullDataToOtherUser() throws Exception { - final WindowState otherUsersWindow = - createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE); - when(mDisplayContent.getTouchableWinAtPointLocked(10, 10)) - .thenReturn(otherUsersWindow); - - dragFlow(0, null, 10, 10); - } - - private void dragFlow(int flag, ClipData data, float dropX, float dropY) { - final SurfaceSession appSession = new SurfaceSession(); - try { - final SurfaceControl surface = new SurfaceControl.Builder(appSession) - .setName("drag surface") - .setSize(100, 100) - .setFormat(PixelFormat.TRANSLUCENT) - .build(); - - assertTrue(sWm.mInputManager.transferTouchFocus(null, null)); - mToken = mTarget.performDrag( - new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, - data); - assertNotNull(mToken); - - mTarget.handleMotionEvent(false, dropX, dropY); - mToken = mWindow.mClient.asBinder(); - } finally { - appSession.kill(); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java deleted file mode 100644 index 96745fa5956e..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/PinnedStackControllerTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.android.server.wm; - -import static android.view.Display.DEFAULT_DISPLAY; - -import android.os.RemoteException; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.IPinnedStackListener; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class PinnedStackControllerTest extends WindowTestsBase { - - @Mock private IPinnedStackListener mIPinnedStackListener; - @Mock private IPinnedStackListener.Stub mIPinnedStackListenerStub; - - @Before - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - when(mIPinnedStackListener.asBinder()).thenReturn(mIPinnedStackListenerStub); - } - - @Test - public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException { - sWm.mSupportsPictureInPicture = true; - sWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener); - - verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0); - verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0); - verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false), - eq(false), anyInt()); - verify(mIPinnedStackListener).onActionsChanged(any()); - verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean()); - - reset(mIPinnedStackListener); - - final int SHELF_HEIGHT = 300; - - sWm.setShelfHeight(true, SHELF_HEIGHT); - verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT); - verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false), - eq(true), anyInt()); - verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java deleted file mode 100644 index a2af9b80fe36..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; -import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.os.Binder; -import android.os.IInterface; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.IRecentsAnimationRunner; -import android.view.SurfaceControl; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * atest FrameworksServicesTests:com.android.server.wm.RecentsAnimationControllerTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class RecentsAnimationControllerTest extends WindowTestsBase { - - @Mock SurfaceControl mMockLeash; - @Mock SurfaceControl.Transaction mMockTransaction; - @Mock OnAnimationFinishedCallback mFinishedCallback; - @Mock IRecentsAnimationRunner mMockRunner; - @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks; - private RecentsAnimationController mController; - - @Before - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - when(mMockRunner.asBinder()).thenReturn(new Binder()); - mController = new RecentsAnimationController(sWm, mMockRunner, mAnimationCallbacks, - DEFAULT_DISPLAY); - } - - @Test - public void testRemovedBeforeStarted_expectCanceled() throws Exception { - final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - AnimationAdapter adapter = mController.addAnimation(appWindow.getTask(), - false /* isRecentTaskInvisible */); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - - // Remove the app window so that the animation target can not be created - appWindow.removeImmediately(); - mController.startAnimation(); - - // Verify that the finish callback to reparent the leash is called - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); - // Verify the animation canceled callback to the app was made - verify(mMockRunner).onAnimationCanceled(); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); - } - - @Test - public void testCancelAfterRemove_expectIgnored() throws Exception { - final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - AnimationAdapter adapter = mController.addAnimation(appWindow.getTask(), - false /* isRecentTaskInvisible */); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - - // Remove the app window so that the animation target can not be created - appWindow.removeImmediately(); - mController.startAnimation(); - mController.cleanupAnimation(REORDER_KEEP_IN_PLACE); - try { - mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test"); - } catch (Exception e) { - fail("Unexpected failure when canceling animation after finishing it"); - } - } - - private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { - verify(binder, atLeast(0)).asBinder(); - verifyNoMoreInteractions(binder); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java deleted file mode 100644 index 95361f03fe4b..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Binder; -import android.os.IInterface; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; - -import com.android.server.testutils.OffsettableClock; -import com.android.server.testutils.TestHandler; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * atest FrameworksServicesTests:com.android.server.wm.RemoteAnimationControllerTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class RemoteAnimationControllerTest extends WindowTestsBase { - - @Mock SurfaceControl mMockLeash; - @Mock Transaction mMockTransaction; - @Mock OnAnimationFinishedCallback mFinishedCallback; - @Mock IRemoteAnimationRunner mMockRunner; - private RemoteAnimationAdapter mAdapter; - private RemoteAnimationController mController; - private final OffsettableClock mClock = new OffsettableClock.Stopped(); - private TestHandler mHandler; - - @Before - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - when(mMockRunner.asBinder()).thenReturn(new Binder()); - mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); - mAdapter.setCallingPid(123); - sWm.mH.runWithScissors(() -> { - mHandler = new TestHandler(null, mClock); - }, 0); - mController = new RemoteAnimationController(sWm, mAdapter, mHandler); - } - - @Test - public void testRun() throws Exception { - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - sWm.mOpeningApps.add(win.mAppToken); - try { - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - mController.goodToGo(); - sWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - final ArgumentCaptor appsCaptor = - ArgumentCaptor.forClass(RemoteAnimationTarget[].class); - final ArgumentCaptor finishedCaptor = - ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); - assertEquals(1, appsCaptor.getValue().length); - final RemoteAnimationTarget app = appsCaptor.getValue()[0]; - assertEquals(new Point(50, 100), app.position); - assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); - assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex); - assertEquals(win.mAppToken.getTask().mTaskId, app.taskId); - assertEquals(mMockLeash, app.leash); - assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); - assertEquals(false, app.isTranslucent); - verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); - verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); - verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 100, 50)); - - finishedCaptor.getValue().onAnimationFinished(); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); - } finally { - sWm.mOpeningApps.clear(); - } - } - - @Test - public void testCancel() throws Exception { - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - mController.goodToGo(); - - adapter.onAnimationCancelled(mMockLeash); - verify(mMockRunner).onAnimationCancelled(); - } - - @Test - public void testTimeout() throws Exception { - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - mController.goodToGo(); - - mClock.fastForward(2500); - mHandler.timeAdvance(); - - verify(mMockRunner).onAnimationCancelled(); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); - } - - @Test - public void testTimeout_scaled() throws Exception { - sWm.setAnimationScale(2, 5.0f); - try{ - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - mController.goodToGo(); - - mClock.fastForward(2500); - mHandler.timeAdvance(); - - verify(mMockRunner, never()).onAnimationCancelled(); - - mClock.fastForward(10000); - mHandler.timeAdvance(); - - verify(mMockRunner).onAnimationCancelled(); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); - } finally { - sWm.setAnimationScale(2, 1.0f); - } - - } - - @Test - public void testZeroAnimations() throws Exception { - mController.goodToGo(); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); - } - - @Test - public void testNotReallyStarted() throws Exception { - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - mController.goodToGo(); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); - } - - @Test - public void testOneNotStarted() throws Exception { - final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); - final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); - mController.createAnimationAdapter(win1.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - mController.goodToGo(); - sWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - final ArgumentCaptor appsCaptor = - ArgumentCaptor.forClass(RemoteAnimationTarget[].class); - final ArgumentCaptor finishedCaptor = - ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); - verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); - assertEquals(1, appsCaptor.getValue().length); - assertEquals(mMockLeash, appsCaptor.getValue()[0].leash); - } - - @Test - public void testRemovedBeforeStarted() throws Exception { - final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); - final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, - new Point(50, 100), new Rect(50, 100, 150, 150)); - adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); - win.mAppToken.removeImmediately(); - mController.goodToGo(); - verifyNoMoreInteractionsExceptAsBinder(mMockRunner); - verify(mFinishedCallback).onAnimationFinished(eq(adapter)); - } - - private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { - verify(binder, atLeast(0)).asBinder(); - verifyNoMoreInteractions(binder); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java deleted file mode 100644 index 204e26cee532..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/RootWindowContainerTests.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.android.server.wm; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.content.res.Configuration; -import android.graphics.Rect; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import static org.junit.Assert.assertTrue; - -/** - * Tests for the {@link RootWindowContainer} class. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.wm.RootWindowContainerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class RootWindowContainerTests extends WindowTestsBase { - @Test - public void testSetDisplayOverrideConfigurationIfNeeded() throws Exception { - synchronized (sWm.mWindowMap) { - // Add first stack we expect to be updated with configuration change. - final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); - stack.getOverrideConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 5, 5)); - - // Add second task that will be set for deferred removal that should not be returned - // with the configuration change. - final TaskStack deferredDeletedStack = createTaskStackOnDisplay(mDisplayContent); - deferredDeletedStack.getOverrideConfiguration().windowConfiguration.setBounds( - new Rect(0, 0, 5, 5)); - deferredDeletedStack.mDeferRemoval = true; - - final Configuration override = new Configuration( - mDisplayContent.getOverrideConfiguration()); - override.windowConfiguration.setBounds(new Rect(0, 0, 10, 10)); - - // Set display override. - final int[] results = sWm.mRoot.setDisplayOverrideConfigurationIfNeeded(override, - mDisplayContent.getDisplayId()); - - // Ensure only first stack is returned. - assertTrue(results.length == 1); - assertTrue(results[0] == stack.mStackId); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java deleted file mode 100644 index a2ccee46e0c9..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.graphics.Color.RED; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; -import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Gravity.BOTTOM; -import static android.view.Gravity.LEFT; -import static android.view.Gravity.RIGHT; -import static android.view.Gravity.TOP; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; -import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import static org.junit.Assert.assertEquals; - -import android.app.Activity; -import android.app.ActivityOptions; -import android.app.Instrumentation; -import android.content.Context; -import android.content.Intent; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.hardware.display.DisplayManager; -import android.hardware.display.VirtualDisplay; -import android.media.ImageReader; -import android.os.Handler; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Pair; -import android.view.Display; -import android.view.DisplayInfo; -import android.view.View; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.widget.TextView; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.function.BooleanSupplier; - -/** - * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.wm.ScreenDecorWindowTests - */ -// TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags. -// TODO: Test non-Activity windows. -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ScreenDecorWindowTests { - - private final Context mContext = InstrumentationRegistry.getTargetContext(); - private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); - - private WindowManager mWm; - private ArrayList mWindows = new ArrayList<>(); - - private Activity mTestActivity; - private VirtualDisplay mDisplay; - private ImageReader mImageReader; - - private int mDecorThickness; - private int mHalfDecorThickness; - - @Before - public void setUp() { - final Pair result = createDisplay(); - mDisplay = result.first; - mImageReader = result.second; - final Display display = mDisplay.getDisplay(); - final Context dContext = mContext.createDisplayContext(display); - mWm = dContext.getSystemService(WindowManager.class); - mTestActivity = startActivityOnDisplay(TestActivity.class, display.getDisplayId()); - final Point size = new Point(); - mDisplay.getDisplay().getRealSize(size); - mDecorThickness = Math.min(size.x, size.y) / 3; - mHalfDecorThickness = mDecorThickness / 2; - } - - @After - public void tearDown() { - while (!mWindows.isEmpty()) { - removeWindow(mWindows.get(0)); - } - finishActivity(mTestActivity); - mDisplay.release(); - mImageReader.close(); - } - - @Test - public void testScreenSides() throws Exception { - // Decor on top - final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); - - // Decor at the bottom - updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0); - assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness); - - // Decor to the left - updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0); - assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness); - - // Decor to the right - updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0); - assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness); - } - - @Test - public void testMultipleDecors() throws Exception { - // Test 2 decor windows on-top. - createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness); - assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness); - createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); - - // And one at the bottom. - createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness); - assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); - assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness); - } - - @Test - public void testFlagChange() throws Exception { - WindowInsets initialInsets = getInsets(mTestActivity); - - final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - assertTopInsetEquals(mTestActivity, mDecorThickness); - - updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, - 0, PRIVATE_FLAG_IS_SCREEN_DECOR); - - // TODO: fix test and re-enable assertion. - // initialInsets was not actually immutable and just updated to the current insets, - // meaning this assertion never actually tested anything. Now that WindowInsets actually is - // immutable, it turns out the test was broken. - // assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); - - updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, - PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR); - assertTopInsetEquals(mTestActivity, mDecorThickness); - } - - @Test - public void testRemoval() throws Exception { - WindowInsets initialInsets = getInsets(mTestActivity); - - final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); - assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); - - removeWindow(decorWindow); - assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); - } - - private View createDecorWindow(int gravity, int width, int height) { - return createWindow("decorWindow", gravity, width, height, RED, - FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR); - } - - private View createWindow(String name, int gravity, int width, int height, int color, int flags, - int privateFlags) { - - final View[] viewHolder = new View[1]; - final int finalFlag = flags - | FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE; - - // Needs to run on the UI thread. - Handler.getMain().runWithScissors(() -> { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - width, height, TYPE_APPLICATION_OVERLAY, finalFlag, PixelFormat.OPAQUE); - lp.gravity = gravity; - lp.privateFlags |= privateFlags; - - final TextView view = new TextView(mContext); - view.setText("ScreenDecorWindowTests - " + name); - view.setBackgroundColor(color); - mWm.addView(view, lp); - mWindows.add(view); - viewHolder[0] = view; - }, 0); - - waitForIdle(); - return viewHolder[0]; - } - - private void updateWindow(View v, int gravity, int width, int height, - int privateFlags, int privateFlagsMask) { - // Needs to run on the UI thread. - Handler.getMain().runWithScissors(() -> { - final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) v.getLayoutParams(); - lp.gravity = gravity; - lp.width = width; - lp.height = height; - setPrivateFlags(lp, privateFlags, privateFlagsMask); - - mWm.updateViewLayout(v, lp); - }, 0); - - waitForIdle(); - } - - private void removeWindow(View v) { - Handler.getMain().runWithScissors(() -> mWm.removeView(v), 0); - mWindows.remove(v); - waitForIdle(); - } - - private WindowInsets getInsets(Activity a) { - return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets()); - } - - /** - * Set the flags of the window, as per the - * {@link WindowManager.LayoutParams WindowManager.LayoutParams} - * flags. - * - * @param flags The new window flags (see WindowManager.LayoutParams). - * @param mask Which of the window flag bits to modify. - */ - public void setPrivateFlags(WindowManager.LayoutParams lp, int flags, int mask) { - lp.flags = (lp.flags & ~mask) | (flags & mask); - } - - /** - * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed. - */ - private void assertTopInsetEquals(Activity activity, int expected) throws Exception { - waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected); - assertEquals(expected, getInsets(activity).getSystemWindowInsetTop()); - } - - /** - * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected} - * waiting as needed. - */ - private void assertInsetGreaterOrEqual(Activity activity, int side, int expected) - throws Exception { - waitFor(() -> { - final WindowInsets insets = getInsets(activity); - switch (side) { - case TOP: return insets.getSystemWindowInsetTop() >= expected; - case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected; - case LEFT: return insets.getSystemWindowInsetLeft() >= expected; - case RIGHT: return insets.getSystemWindowInsetRight() >= expected; - default: return true; - } - }); - - final WindowInsets insets = getInsets(activity); - switch (side) { - case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break; - case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break; - case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break; - case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break; - } - } - - /** Asserts that the first entry is greater than or equal to the second entry. */ - private void assertGreaterOrEqual(int first, int second) throws Exception { - Assert.assertTrue("Excepted " + first + " >= " + second, first >= second); - } - - private void waitFor(BooleanSupplier waitCondition) { - int retriesLeft = 5; - do { - if (waitCondition.getAsBoolean()) { - break; - } - try { - Thread.sleep(500); - } catch (InterruptedException e) { - // Well I guess we are not waiting... - } - } while (retriesLeft-- > 0); - } - - private void finishActivity(Activity a) { - if (a == null) { - return; - } - a.finish(); - waitForIdle(); - } - - private void waitForIdle() { - mInstrumentation.waitForIdleSync(); - } - - private Activity startActivityOnDisplay(Class cls, int displayId) { - final Intent intent = new Intent(mContext, cls); - intent.addFlags(FLAG_ACTIVITY_NEW_TASK); - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchDisplayId(displayId); - final Activity activity = mInstrumentation.startActivitySync(intent, options.toBundle()); - waitForIdle(); - - assertEquals(displayId, activity.getDisplay().getDisplayId()); - return activity; - } - - private Pair createDisplay() { - final DisplayManager dm = mContext.getSystemService(DisplayManager.class); - final DisplayInfo displayInfo = new DisplayInfo(); - final Display defaultDisplay = dm.getDisplay(DEFAULT_DISPLAY); - defaultDisplay.getDisplayInfo(displayInfo); - final String name = "ScreenDecorWindowTests"; - int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; - - final ImageReader imageReader = ImageReader.newInstance( - displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2); - - final VirtualDisplay display = dm.createVirtualDisplay(name, displayInfo.logicalWidth, - displayInfo.logicalHeight, displayInfo.logicalDensityDpi, imageReader.getSurface(), - flags); - - return Pair.create(display, imageReader); - } - - public static class TestActivity extends Activity { - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java deleted file mode 100644 index ab0a2bd86dd8..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.graphics.Rect; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Test class for {@link StackWindowController}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:StackWindowControllerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class StackWindowControllerTests extends WindowTestsBase { - @Test - public void testRemoveContainer() throws Exception { - final StackWindowController stackController = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController); - - final TaskStack stack = stackController.mContainer; - final Task task = taskController.mContainer; - assertNotNull(stack); - assertNotNull(task); - stackController.removeContainer(); - // Assert that the container was removed. - assertNull(stackController.mContainer); - assertNull(taskController.mContainer); - assertNull(stack.getDisplayContent()); - assertNull(task.getDisplayContent()); - assertNull(task.mStack); - } - - @Test - public void testRemoveContainer_deferRemoval() throws Exception { - final StackWindowController stackController = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController); - - final TaskStack stack = stackController.mContainer; - final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; - // Stack removal is deferred if one of its child is animating. - task.setLocalIsAnimating(true); - - stackController.removeContainer(); - // For the case of deferred removal the stack controller will no longer be connected to the - // container, but the task controller will still be connected to the its container until - // the stack window container is removed. - assertNull(stackController.mContainer); - assertNull(stack.getController()); - assertNotNull(taskController.mContainer); - assertNotNull(task.getController()); - - stack.removeImmediately(); - assertNull(taskController.mContainer); - assertNull(task.getController()); - } - - @Test - public void testReparent() throws Exception { - // Create first stack on primary display. - final StackWindowController stack1Controller = - createStackControllerOnDisplay(mDisplayContent); - final TaskStack stack1 = stack1Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); - final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; - task1.mOnDisplayChangedCalled = false; - - // Create second display and put second stack on it. - final DisplayContent dc = createNewDisplay(); - final StackWindowController stack2Controller = - createStackControllerOnDisplay(dc); - final TaskStack stack2 = stack2Controller.mContainer; - - // Reparent - stack1Controller.reparent(dc.getDisplayId(), new Rect(), true /* onTop */); - assertEquals(dc, stack1.getDisplayContent()); - final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); - final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); - assertEquals(stack1PositionInParent, stack2PositionInParent + 1); - assertTrue(task1.mOnDisplayChangedCalled); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java deleted file mode 100644 index edac8a5202d7..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.animation.AnimationHandler; -import android.animation.AnimationHandler.AnimationFrameCallbackProvider; -import android.animation.ValueAnimator; -import android.graphics.Matrix; -import android.graphics.Point; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Log; -import android.view.Choreographer; -import android.view.Choreographer.FrameCallback; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; - -import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; -import com.android.server.wm.SurfaceAnimationRunner.AnimatorFactory; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.concurrent.CountDownLatch; - -/** - * Test class for {@link SurfaceAnimationRunner}. - * - * atest FrameworksServicesTests:com.android.server.wm.SurfaceAnimationRunnerTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class SurfaceAnimationRunnerTest extends WindowTestsBase { - - @Mock SurfaceControl mMockSurface; - @Mock Transaction mMockTransaction; - @Mock AnimationSpec mMockAnimationSpec; - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - - private SurfaceAnimationRunner mSurfaceAnimationRunner; - private CountDownLatch mFinishCallbackLatch; - - @Before - public void setUp() throws Exception { - super.setUp(); - mFinishCallbackLatch = new CountDownLatch(1); - mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null, - mMockTransaction); - } - - private void finishedCallback() { - mFinishCallbackLatch.countDown(); - } - - @Test - public void testAnimation() throws Exception { - mSurfaceAnimationRunner - .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction, - this::finishedCallback); - - // Ensure that the initial transformation has been applied. - final Matrix m = new Matrix(); - m.setTranslate(-10, 0); - verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any()); - verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f)); - - mFinishCallbackLatch.await(1, SECONDS); - assertFinishCallbackCalled(); - - m.setTranslate(10, 0); - verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any()); - - // At least 3 times: After initialization, first frame, last frame. - verify(mMockTransaction, atLeast(3)).setAlpha(eq(mMockSurface), eq(1.0f)); - } - - @Test - public void testCancel_notStarted() throws Exception { - mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null, - mMockTransaction); - mSurfaceAnimationRunner - .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction, - this::finishedCallback); - mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); - waitUntilHandlersIdle(); - assertTrue(mSurfaceAnimationRunner.mPendingAnimations.isEmpty()); - assertFinishCallbackNotCalled(); - } - - @Test - public void testCancel_running() throws Exception { - mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null, - mMockTransaction); - mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface, - mMockTransaction, this::finishedCallback); - waitUntilNextFrame(); - assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); - mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); - assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); - waitUntilHandlersIdle(); - assertFinishCallbackNotCalled(); - } - - @FlakyTest(bugId = 71719744) - @Test - public void testCancel_sneakyCancelBeforeUpdate() throws Exception { - mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() { - { - setFloatValues(0f, 1f); - } - - @Override - public void addUpdateListener(AnimatorUpdateListener listener) { - super.addUpdateListener(animation -> { - // Sneaky test cancels animation just before applying frame to simulate - // interleaving of multiple threads. Muahahaha - if (animation.getCurrentPlayTime() > 0) { - mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); - } - listener.onAnimationUpdate(animation); - }); - } - }, mMockTransaction); - when(mMockAnimationSpec.getDuration()).thenReturn(200L); - mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction, - this::finishedCallback); - - // We need to wait for two frames: The first frame starts the animation, the second frame - // actually cancels the animation. - waitUntilNextFrame(); - waitUntilNextFrame(); - assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); - verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L)); - } - - @FlakyTest(bugId = 74780584) - @Test - public void testDeferStartingAnimations() throws Exception { - mSurfaceAnimationRunner.deferStartingAnimations(); - mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface, - mMockTransaction, this::finishedCallback); - waitUntilNextFrame(); - assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); - mSurfaceAnimationRunner.continueStartingAnimations(); - waitUntilNextFrame(); - assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); - mFinishCallbackLatch.await(1, SECONDS); - assertFinishCallbackCalled(); - } - - private void waitUntilNextFrame() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, - latch::countDown, null /* token */); - latch.await(); - } - - private void assertFinishCallbackCalled() { - assertEquals(0, mFinishCallbackLatch.getCount()); - } - - private void assertFinishCallbackNotCalled() { - assertEquals(1, mFinishCallbackLatch.getCount()); - } - - private AnimationSpec createTranslateAnimation() { - final Animation a = new TranslateAnimation(-10, 10, 0, 0); - a.initialize(0, 0, 0, 0); - a.setDuration(50); - return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */); - } - - /** - * Callback provider that doesn't animate at all. - */ - private static final class NoOpFrameCallbackProvider implements AnimationFrameCallbackProvider { - - @Override - public void postFrameCallback(FrameCallback callback) { - } - - @Override - public void postCommitCallback(Runnable runnable) { - } - - @Override - public long getFrameTime() { - return 0; - } - - @Override - public long getFrameDelay() { - return 0; - } - - @Override - public void setFrameDelay(long delay) { - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java deleted file mode 100644 index 16b84581de39..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimatorTest.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Builder; -import android.view.SurfaceControl.Transaction; -import android.view.SurfaceSession; - -import com.android.server.wm.SurfaceAnimator.Animatable; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; - -/** - * Test class for {@link SurfaceAnimatorTest}. - * - * atest FrameworksServicesTests:com.android.server.wm.SurfaceAnimatorTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class SurfaceAnimatorTest extends WindowTestsBase { - - @Mock AnimationAdapter mSpec; - @Mock AnimationAdapter mSpec2; - @Mock Transaction mTransaction; - - private SurfaceSession mSession = new SurfaceSession(); - private MyAnimatable mAnimatable; - private MyAnimatable mAnimatable2; - private DeferFinishAnimatable mDeferFinishAnimatable; - - @Before - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - mAnimatable = new MyAnimatable(); - mAnimatable2 = new MyAnimatable(); - mDeferFinishAnimatable = new DeferFinishAnimatable(); - } - - @Test - public void testRunAnimation() throws Exception { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( - OnAnimationFinishedCallback.class); - assertAnimating(mAnimatable); - verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle())); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); - - callbackCaptor.getValue().onAnimationFinished(mSpec); - assertNotAnimating(mAnimatable); - assertTrue(mAnimatable.mFinishedCallbackCalled); - verify(mTransaction).destroy(eq(mAnimatable.mLeash)); - // TODO: Verify reparenting once we use mPendingTransaction to reparent it back - } - - @Test - public void testOverrideAnimation() throws Exception { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - final SurfaceControl firstLeash = mAnimatable.mLeash; - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */); - - verify(mTransaction).destroy(eq(firstLeash)); - assertFalse(mAnimatable.mFinishedCallbackCalled); - - final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( - OnAnimationFinishedCallback.class); - assertAnimating(mAnimatable); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); - - // First animation was finished, but this shouldn't cancel the second animation - callbackCaptor.getValue().onAnimationFinished(mSpec); - assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); - - // Second animation was finished - verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture()); - callbackCaptor.getValue().onAnimationFinished(mSpec2); - assertNotAnimating(mAnimatable); - assertTrue(mAnimatable.mFinishedCallbackCalled); - } - - @Test - public void testCancelAnimation() throws Exception { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - assertAnimating(mAnimatable); - mAnimatable.mSurfaceAnimator.cancelAnimation(); - assertNotAnimating(mAnimatable); - verify(mSpec).onAnimationCancelled(any()); - assertTrue(mAnimatable.mFinishedCallbackCalled); - verify(mTransaction).destroy(eq(mAnimatable.mLeash)); - } - - @Test - public void testDelayingAnimationStart() throws Exception { - mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - verifyZeroInteractions(mSpec); - assertAnimating(mAnimatable); - assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed()); - mAnimatable.mSurfaceAnimator.endDelayingAnimationStart(); - verify(mSpec).startAnimation(any(), any(), any()); - } - - @Test - public void testDelayingAnimationStartAndCancelled() throws Exception { - mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - mAnimatable.mSurfaceAnimator.cancelAnimation(); - verifyZeroInteractions(mSpec); - assertNotAnimating(mAnimatable); - assertTrue(mAnimatable.mFinishedCallbackCalled); - verify(mTransaction).destroy(eq(mAnimatable.mLeash)); - } - - @Test - public void testTransferAnimation() throws Exception { - mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); - - final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( - OnAnimationFinishedCallback.class); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); - final SurfaceControl leash = mAnimatable.mLeash; - - mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator); - assertNotAnimating(mAnimatable); - assertAnimating(mAnimatable2); - assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash); - verify(mTransaction, never()).destroy(eq(leash)); - callbackCaptor.getValue().onAnimationFinished(mSpec); - assertNotAnimating(mAnimatable2); - assertTrue(mAnimatable2.mFinishedCallbackCalled); - verify(mTransaction).destroy(eq(leash)); - } - - @Test - @FlakyTest(detail = "Promote once confirmed non-flaky") - public void testDeferFinish() throws Exception { - - // Start animation - mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, - true /* hidden */); - final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( - OnAnimationFinishedCallback.class); - assertAnimating(mDeferFinishAnimatable); - verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); - - // Finish the animation but then make sure we are deferring. - callbackCaptor.getValue().onAnimationFinished(mSpec); - assertAnimating(mDeferFinishAnimatable); - - // Now end defer finishing. - mDeferFinishAnimatable.endDeferFinishCallback.run(); - assertNotAnimating(mAnimatable2); - assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled); - verify(mTransaction).destroy(eq(mDeferFinishAnimatable.mLeash)); - } - - private void assertAnimating(MyAnimatable animatable) { - assertTrue(animatable.mSurfaceAnimator.isAnimating()); - assertNotNull(animatable.mSurfaceAnimator.getAnimation()); - } - - private void assertNotAnimating(MyAnimatable animatable) { - assertFalse(animatable.mSurfaceAnimator.isAnimating()); - assertNull(animatable.mSurfaceAnimator.getAnimation()); - } - - private class MyAnimatable implements Animatable { - - final SurfaceControl mParent; - final SurfaceControl mSurface; - final SurfaceAnimator mSurfaceAnimator; - SurfaceControl mLeash; - boolean mFinishedCallbackCalled; - - MyAnimatable() { - mParent = sWm.makeSurfaceBuilder(mSession) - .setName("test surface parent") - .setSize(3000, 3000) - .build(); - mSurface = sWm.makeSurfaceBuilder(mSession) - .setName("test surface") - .setSize(1, 1) - .build(); - mFinishedCallbackCalled = false; - mLeash = null; - mSurfaceAnimator = new SurfaceAnimator(this, mFinishedCallback, sWm); - } - - @Override - public Transaction getPendingTransaction() { - return mTransaction; - } - - @Override - public void commitPendingTransaction() { - } - - @Override - public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { - } - - @Override - public void onAnimationLeashDestroyed(Transaction t) { - } - - @Override - public Builder makeAnimationLeash() { - return new SurfaceControl.Builder(mSession) { - - @Override - public SurfaceControl build() { - mLeash = super.build(); - return mLeash; - } - }.setParent(mParent); - } - - @Override - public SurfaceControl getAnimationLeashParent() { - return mParent; - } - - @Override - public SurfaceControl getSurfaceControl() { - return mSurface; - } - - @Override - public SurfaceControl getParentSurfaceControl() { - return mParent; - } - - @Override - public int getSurfaceWidth() { - return 1; - } - - @Override - public int getSurfaceHeight() { - return 1; - } - - private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true; - } - - private class DeferFinishAnimatable extends MyAnimatable { - - Runnable endDeferFinishCallback; - - @Override - public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { - this.endDeferFinishCallback = endDeferFinishCallback; - return true; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java deleted file mode 100644 index 7bf7dd78711c..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.graphics.Rect; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; - -import static com.android.server.wm.TaskPositioner.MIN_ASPECT; -import static com.android.server.wm.WindowManagerService.dipToPixel; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Tests for the {@link TaskPositioner} class. - * - * runtest frameworks-services -c com.android.server.wm.TaskPositionerTests - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TaskPositionerTests extends WindowTestsBase { - - private final boolean DEBUGGING = false; - private final String TAG = "TaskPositionerTest"; - - private final static int MOUSE_DELTA_X = 5; - private final static int MOUSE_DELTA_Y = 5; - - private int mMinVisibleWidth; - private int mMinVisibleHeight; - private TaskPositioner mPositioner; - - @Before - public void setUp() throws Exception { - super.setUp(); - - TaskPositioner.setFactory(null); - - final Display display = mDisplayContent.getDisplay(); - final DisplayMetrics dm = new DisplayMetrics(); - display.getMetrics(dm); - - // This should be the same calculation as the TaskPositioner uses. - mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm); - mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm); - - mPositioner = TaskPositioner.create(sWm); - mPositioner.register(mDisplayContent); - } - - @Test - public void testOverrideFactory() throws Exception { - final boolean[] created = new boolean[1]; - created[0] = false; - TaskPositioner.setFactory(new TaskPositioner.Factory() { - @Override - public TaskPositioner create(WindowManagerService service) { - created[0] = true; - return null; - } - }); - - assertNull(TaskPositioner.create(sWm)); - assertTrue(created[0]); - } - - /** - * This tests that free resizing will allow to change the orientation as well - * as does some basic tests (e.g. dragging in Y only will keep X stable). - */ - @Test - @Ignore - public void testBasicFreeWindowResizing() throws Exception { - final Rect r = new Rect(100, 220, 700, 520); - final int midY = (r.top + r.bottom) / 2; - - // Start a drag resize starting upper left. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the width. - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals( - new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height. - mPositioner.resizeDrag(r.left, 2000.0f); - assertBoundsEquals( - new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Start a drag resize left and see that only the left coord changes.. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(200.0f, midY); - assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored. - */ - @Test - @Ignore - public void testFreeWindowResizingTestAllEdges() throws Exception { - final Rect r = new Rect(100, 220, 700, 520); - final int midX = (r.left + r.right) / 2; - final int midY = (r.top + r.bottom) / 2; - - // Drag upper left. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); - mPositioner.resizeDrag(0.0f, 0.0f); - assertTrue(r.left != mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertTrue(r.top != mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag upper. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r); - mPositioner.resizeDrag(0.0f, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertTrue(r.top != mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag upper right. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); - mPositioner.resizeDrag(r.right + 100, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertTrue(r.right != mPositioner.getWindowDragBounds().right); - assertTrue(r.top != mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag right. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, midY, r); - mPositioner.resizeDrag(r.right + 100, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertTrue(r.right != mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom right. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, - r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r); - mPositioner.resizeDrag(r.right + 100, r.bottom + 100); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertTrue(r.right != mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom); - - // Drag bottom. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, midX, r.bottom + MOUSE_DELTA_Y, r); - mPositioner.resizeDrag(r.right + 100, r.bottom + 100); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom); - - // Drag bottom left. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r); - mPositioner.resizeDrag(0.0f, r.bottom + 100); - assertTrue(r.left != mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom); - - // Drag left. - mPositioner.startDrag(true /*resizing*/, - false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midX, r); - mPositioner.resizeDrag(0.0f, r.bottom + 100); - assertTrue(r.left != mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the top left corner. - */ - @Test - @Ignore - public void testLandscapePreservedWindowResizingDragTopLeft() throws Exception { - final Rect r = new Rect(100, 220, 700, 520); - - mPositioner.startDrag(true /*resizing*/, - true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the width. - mPositioner.resizeDrag(2000.0f, r.top); - final int w = mMinVisibleWidth; - final int h = Math.round(w / MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height. - mPositioner.resizeDrag(r.left, 2000.0f); - assertBoundsEquals( - new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the left corner. - */ - @Test - @Ignore - public void testLandscapePreservedWindowResizingDragLeft() throws Exception { - final Rect r = new Rect(100, 220, 700, 520); - final int midY = (r.top + r.bottom) / 2; - - mPositioner.startDrag(true /*resizing*/, - true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(200.0f, midY); - assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag all the way to the right and see the height also shrinking. - mPositioner.resizeDrag(2000.0f, midY); - final int w = mMinVisibleWidth; - final int h = Math.round((float)w / MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the top corner. - */ - @Test - @Ignore - public void testLandscapePreservedWindowResizingDragTop() throws Exception { - final Rect r = new Rect(100, 220, 700, 520); - final int midX = (r.left + r.right) / 2; - - mPositioner.startDrag(true /*resizing*/, - true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r); - - // Drag to the left (no change). - mPositioner.resizeDrag(0.0f, r.top); - assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right (no change). - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(300.0f, 0.0f); - int h = r.bottom - MOUSE_DELTA_Y; - int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT)); - assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - h = mMinVisibleHeight; - assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the top left corner. - */ - @Test - @Ignore - public void testPortraitPreservedWindowResizingDragTopLeft() throws Exception { - final Rect r = new Rect(330, 100, 630, 600); - - mPositioner.startDrag(true /*resizing*/, - true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(500.0f, 0.0f); - assertBoundsEquals(new Rect(500 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height and the the width shrinking. - mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f); - final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT)); - final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT)); - assertBoundsEquals( - new Rect(r.right - w, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the left corner. - */ - @Test - @Ignore - public void testPortraitPreservedWindowResizingDragLeft() throws Exception { - final Rect r = new Rect(330, 100, 630, 600); - final int midY = (r.top + r.bottom) / 2; - - mPositioner.startDrag(true /*resizing*/, - true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - int w = r.right - MOUSE_DELTA_X; - int h = Math.round(w * MIN_ASPECT); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(450.0f, midY); - assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag all the way to the right. - mPositioner.resizeDrag(2000.0f, midY); - w = mMinVisibleWidth; - h = Math.max(Math.round((float)w * MIN_ASPECT), r.height()); - assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the top corner. - */ - @Test - @Ignore - public void testPortraitPreservedWindowResizingDragTop() throws Exception { - final Rect r = new Rect(330, 100, 630, 600); - final int midX = (r.left + r.right) / 2; - - mPositioner.startDrag(true /*resizing*/, - true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r); - - // Drag to the left (no change). - mPositioner.resizeDrag(0.0f, r.top); - assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right (no change). - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(300.0f, 0.0f); - int h = r.bottom - MOUSE_DELTA_Y; - int w = Math.min(r.width(), Math.round(h / MIN_ASPECT)); - assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT)); - w = Math.round(h / MIN_ASPECT); - assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - } - - private void assertBoundsEquals(Rect expected, Rect actual) { - if (DEBUGGING) { - if (!expected.equals(actual)) { - Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString() - + ") " + Log.getStackTraceString(new Throwable())); - } - } - assertEquals(expected.left, actual.left); - assertEquals(expected.right, actual.right); - assertEquals(expected.top, actual.top); - assertEquals(expected.bottom, actual.bottom); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java deleted file mode 100644 index 6070516669b5..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.InputChannel; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link TaskPositioningController} class. - * - * atest com.android.server.wm.TaskPositioningControllerTests - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -@Presubmit -public class TaskPositioningControllerTests extends WindowTestsBase { - private static final int TIMEOUT_MS = 1000; - private TaskPositioningController mTarget; - private WindowState mWindow; - - @Before - public void setUp() throws Exception { - super.setUp(); - - assertNotNull(sWm.mTaskPositioningController); - mTarget = sWm.mTaskPositioningController; - - when(sWm.mInputManager.transferTouchFocus( - any(InputChannel.class), - any(InputChannel.class))).thenReturn(true); - - mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); - mWindow.mInputChannel = new InputChannel(); - synchronized (sWm.mWindowMap) { - sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); - } - } - - @Test - public void testStartAndFinishPositioning() throws Exception { - synchronized (sWm.mWindowMap) { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0)); - - synchronized (sWm.mWindowMap) { - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - } - - mTarget.finishTaskPositioning(); - // Wait until the looper processes finishTaskPositioning. - assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testHandleTapOutsideTask() throws Exception { - synchronized (sWm.mWindowMap) { - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - final DisplayContent content = mock(DisplayContent.class); - when(content.findTaskForResizePoint(anyInt(), anyInt())).thenReturn(mWindow.getTask()); - assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); - - mTarget.handleTapOutsideTask(content, 0, 0); - // Wait until the looper processes finishTaskPositioning. - assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS)); - - synchronized (sWm.mWindowMap) { - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - } - - mTarget.finishTaskPositioning(); - // Wait until the looper processes finishTaskPositioning. - assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java deleted file mode 100644 index 649de4a783fa..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link TaskSnapshotCache}. - * - * runtest frameworks-services -c com.android.server.wm.TaskSnapshotCacheTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { - - private TaskSnapshotCache mCache; - - @Before - public void setUp() throws Exception { - super.setUp(); - mCache = new TaskSnapshotCache(sWm, mLoader); - } - - @Test - public void testAppRemoved() throws Exception { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); - mCache.putSnapshot(window.getTask(), createSnapshot()); - assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, - false /* restoreFromDisk */, false /* reducedResolution */)); - mCache.onAppRemoved(window.mAppToken); - assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, - false /* restoreFromDisk */, false /* reducedResolution */)); - } - - @Test - public void testAppDied() throws Exception { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); - mCache.putSnapshot(window.getTask(), createSnapshot()); - assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, - false /* restoreFromDisk */, false /* reducedResolution */)); - mCache.onAppDied(window.mAppToken); - assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, - false /* restoreFromDisk */, false /* reducedResolution */)); - } - - @Test - public void testTaskRemoved() throws Exception { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); - mCache.putSnapshot(window.getTask(), createSnapshot()); - assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, - false /* restoreFromDisk */, false /* reducedResolution */)); - mCache.onTaskRemoved(window.getTask().mTaskId); - assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, - false /* restoreFromDisk */, false /* reducedResolution */)); - } - - @Test - public void testReduced_notCached() throws Exception { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); - mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot()); - mPersister.waitForQueueEmpty(); - assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, - false /* restoreFromDisk */, false /* reducedResolution */)); - - // Load it from disk - assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, - true /* restoreFromDisk */, true /* reducedResolution */)); - - // Make sure it's not in the cache now. - assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, - false /* restoreFromDisk */, false /* reducedResolution */)); - } - - @Test - public void testRestoreFromDisk() throws Exception { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); - mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot()); - mPersister.waitForQueueEmpty(); - assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, - false /* restoreFromDisk */, false /* reducedResolution */)); - - // Load it from disk - assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, - true /* restoreFromDisk */, false /* reducedResolution */)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java deleted file mode 100644 index 5650050f0420..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.WindowManager.TRANSIT_UNSET; -import static com.android.server.wm.TaskSnapshotController.*; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.ArraySet; - -import com.google.android.collect.Sets; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link TaskSnapshotController}. - * - * runtest frameworks-services -c com.android.server.wm.TaskSnapshotControllerTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskSnapshotControllerTest extends WindowTestsBase { - - @Test - public void testGetClosingApps_closing() throws Exception { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); - closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, - true /* performLayout */, false /* isVoiceInteraction */); - final ArraySet closingApps = new ArraySet<>(); - closingApps.add(closingWindow.mAppToken); - final ArraySet closingTasks = new ArraySet<>(); - sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks); - assertEquals(1, closingTasks.size()); - assertEquals(closingWindow.mAppToken.getTask(), closingTasks.valueAt(0)); - } - - @Test - public void testGetClosingApps_notClosing() throws Exception { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); - final WindowState openingWindow = createAppWindow(closingWindow.getTask(), - FIRST_APPLICATION_WINDOW, "openingWindow"); - closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, - true /* performLayout */, false /* isVoiceInteraction */); - openingWindow.mAppToken.setVisibility(null, true /* visible */, TRANSIT_UNSET, - true /* performLayout */, false /* isVoiceInteraction */); - final ArraySet closingApps = new ArraySet<>(); - closingApps.add(closingWindow.mAppToken); - final ArraySet closingTasks = new ArraySet<>(); - sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks); - assertEquals(0, closingTasks.size()); - } - - @Test - public void testGetClosingApps_skipClosingAppsSnapshotTasks() throws Exception { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); - closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, - true /* performLayout */, false /* isVoiceInteraction */); - final ArraySet closingApps = new ArraySet<>(); - closingApps.add(closingWindow.mAppToken); - final ArraySet closingTasks = new ArraySet<>(); - sWm.mTaskSnapshotController.addSkipClosingAppSnapshotTasks( - Sets.newArraySet(closingWindow.mAppToken.getTask())); - sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks); - assertEquals(0, closingTasks.size()); - } - - @Test - public void testGetSnapshotMode() throws Exception { - final WindowState disabledWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow"); - disabledWindow.mAppToken.setDisablePreviewScreenshots(true); - assertEquals(SNAPSHOT_MODE_APP_THEME, - sWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask())); - - final WindowState normalWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow"); - assertEquals(SNAPSHOT_MODE_REAL, - sWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask())); - - final WindowState secureWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "secureWindow"); - secureWindow.mAttrs.flags |= FLAG_SECURE; - assertEquals(SNAPSHOT_MODE_APP_THEME, - sWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java deleted file mode 100644 index 325d42aa6293..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.app.ActivityManager.TaskSnapshot; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.SystemClock; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.ArraySet; - -import android.view.View; -import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -/** - * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader} - * - * atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest - */ -@MediumTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase { - - private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40); - - @Test - public void testPersistAndLoadSnapshot() { - mPersister.persistSnapshot(1 , mTestUserId, createSnapshot()); - mPersister.waitForQueueEmpty(); - final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"), - new File(sFilesDir.getPath() + "/snapshots/1.jpg"), - new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")}; - assertTrueForFiles(files, File::exists, " must exist"); - final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* reduced */); - assertNotNull(snapshot); - assertEquals(TEST_INSETS, snapshot.getContentInsets()); - assertNotNull(snapshot.getSnapshot()); - assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation()); - } - - private void assertTrueForFiles(File[] files, Predicate predicate, String message) { - for (File file : files) { - assertTrue(file.getName() + message, predicate.apply(file)); - } - } - - @Test - public void testTaskRemovedFromRecents() { - mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); - mPersister.onTaskRemovedFromRecents(1, mTestUserId); - mPersister.waitForQueueEmpty(); - assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists()); - assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists()); - assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists()); - } - - /** - * Tests that persisting a couple of snapshots is being throttled. - */ - @Test - public void testThrottling() { - long ms = SystemClock.elapsedRealtime(); - mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); - mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); - mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); - mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); - mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); - mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); - mPersister.waitForQueueEmpty(); - assertTrue(SystemClock.elapsedRealtime() - ms > 500); - } - - /** - * Tests that too many store write queue items are being purged. - */ - @Test - public void testPurging() { - mPersister.persistSnapshot(100, mTestUserId, createSnapshot()); - mPersister.waitForQueueEmpty(); - mPersister.setPaused(true); - mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); - mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); - mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); - mPersister.persistSnapshot(3, mTestUserId, createSnapshot()); - mPersister.persistSnapshot(4, mTestUserId, createSnapshot()); - mPersister.setPaused(false); - mPersister.waitForQueueEmpty(); - - // Make sure 1,2 were purged but removeObsoleteFiles wasn't. - final File[] existsFiles = new File[] { - new File(sFilesDir.getPath() + "/snapshots/3.proto"), - new File(sFilesDir.getPath() + "/snapshots/4.proto")}; - final File[] nonExistsFiles = new File[] { - new File(sFilesDir.getPath() + "/snapshots/100.proto"), - new File(sFilesDir.getPath() + "/snapshots/1.proto"), - new File(sFilesDir.getPath() + "/snapshots/1.proto")}; - assertTrueForFiles(existsFiles, File::exists, " must exist"); - assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); - } - - @Test - public void testGetTaskId() { - RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem = - mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[] {}); - assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp")); - assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err")); - assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/")); - assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg")); - assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto")); - assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg")); - assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg")); - } - - @Test - public void testLowResolutionPersistAndLoadSnapshot() { - TaskSnapshot a = createSnapshot(0.5f /* reducedResolution */); - assertTrue(a.isReducedResolution()); - mPersister.persistSnapshot(1 , mTestUserId, a); - mPersister.waitForQueueEmpty(); - final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"), - new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")}; - final File[] nonExistsFiles = new File[] { - new File(sFilesDir.getPath() + "/snapshots/1.jpg"), - }; - assertTrueForFiles(files, File::exists, " must exist"); - assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); - final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, true /* reduced */); - assertNotNull(snapshot); - assertEquals(TEST_INSETS, snapshot.getContentInsets()); - assertNotNull(snapshot.getSnapshot()); - assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation()); - - final TaskSnapshot snapshotNotExist = mLoader.loadTask(1, mTestUserId, false /* reduced */); - assertNull(snapshotNotExist); - } - - @Test - public void testIsRealSnapshotPersistAndLoadSnapshot() { - TaskSnapshot a = new TaskSnapshotBuilder() - .setIsRealSnapshot(true) - .build(); - TaskSnapshot b = new TaskSnapshotBuilder() - .setIsRealSnapshot(false) - .build(); - assertTrue(a.isRealSnapshot()); - assertFalse(b.isRealSnapshot()); - mPersister.persistSnapshot(1, mTestUserId, a); - mPersister.persistSnapshot(2, mTestUserId, b); - mPersister.waitForQueueEmpty(); - final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); - final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); - assertNotNull(snapshotA); - assertNotNull(snapshotB); - assertTrue(snapshotA.isRealSnapshot()); - assertFalse(snapshotB.isRealSnapshot()); - } - - @Test - public void testWindowingModePersistAndLoadSnapshot() { - TaskSnapshot a = new TaskSnapshotBuilder() - .setWindowingMode(WINDOWING_MODE_FULLSCREEN) - .build(); - TaskSnapshot b = new TaskSnapshotBuilder() - .setWindowingMode(WINDOWING_MODE_PINNED) - .build(); - assertTrue(a.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - assertTrue(b.getWindowingMode() == WINDOWING_MODE_PINNED); - mPersister.persistSnapshot(1, mTestUserId, a); - mPersister.persistSnapshot(2, mTestUserId, b); - mPersister.waitForQueueEmpty(); - final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); - final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); - assertNotNull(snapshotA); - assertNotNull(snapshotB); - assertTrue(snapshotA.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); - assertTrue(snapshotB.getWindowingMode() == WINDOWING_MODE_PINNED); - } - - @Test - public void testIsTranslucentPersistAndLoadSnapshot() { - TaskSnapshot a = new TaskSnapshotBuilder() - .setIsTranslucent(true) - .build(); - TaskSnapshot b = new TaskSnapshotBuilder() - .setIsTranslucent(false) - .build(); - assertTrue(a.isTranslucent()); - assertFalse(b.isTranslucent()); - mPersister.persistSnapshot(1, mTestUserId, a); - mPersister.persistSnapshot(2, mTestUserId, b); - mPersister.waitForQueueEmpty(); - final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); - final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); - assertNotNull(snapshotA); - assertNotNull(snapshotB); - assertTrue(snapshotA.isTranslucent()); - assertFalse(snapshotB.isTranslucent()); - } - - @Test - public void testSystemUiVisibilityPersistAndLoadSnapshot() { - final int lightBarFlags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - TaskSnapshot a = new TaskSnapshotBuilder() - .setSystemUiVisibility(0) - .build(); - TaskSnapshot b = new TaskSnapshotBuilder() - .setSystemUiVisibility(lightBarFlags) - .build(); - assertTrue(a.getSystemUiVisibility() == 0); - assertTrue(b.getSystemUiVisibility() == lightBarFlags); - mPersister.persistSnapshot(1, mTestUserId, a); - mPersister.persistSnapshot(2, mTestUserId, b); - mPersister.waitForQueueEmpty(); - final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); - final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); - assertNotNull(snapshotA); - assertNotNull(snapshotB); - assertTrue(snapshotA.getSystemUiVisibility() == 0); - assertTrue(snapshotB.getSystemUiVisibility() == lightBarFlags); - } - - @Test - public void testRemoveObsoleteFiles() { - mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); - mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); - final ArraySet taskIds = new ArraySet<>(); - taskIds.add(1); - mPersister.removeObsoleteFiles(taskIds, new int[] { mTestUserId }); - mPersister.waitForQueueEmpty(); - final File[] existsFiles = new File[] { - new File(sFilesDir.getPath() + "/snapshots/1.proto"), - new File(sFilesDir.getPath() + "/snapshots/1.jpg"), - new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") }; - final File[] nonExistsFiles = new File[] { - new File(sFilesDir.getPath() + "/snapshots/2.proto"), - new File(sFilesDir.getPath() + "/snapshots/2.jpg"), - new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")}; - assertTrueForFiles(existsFiles, File::exists, " must exist"); - assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); - } - - @Test - public void testRemoveObsoleteFiles_addedOneInTheMeantime() { - mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); - final ArraySet taskIds = new ArraySet<>(); - taskIds.add(1); - mPersister.removeObsoleteFiles(taskIds, new int[] { mTestUserId }); - mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); - mPersister.waitForQueueEmpty(); - final File[] existsFiles = new File[] { - new File(sFilesDir.getPath() + "/snapshots/1.proto"), - new File(sFilesDir.getPath() + "/snapshots/1.jpg"), - new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"), - new File(sFilesDir.getPath() + "/snapshots/2.proto"), - new File(sFilesDir.getPath() + "/snapshots/2.jpg"), - new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")}; - assertTrueForFiles(existsFiles, File::exists, " must exist"); - } - - /** - * Private predicate definition. - * - * This is needed because com.android.internal.util.Predicate is deprecated - * and can only be used with classes fron android.test.runner. This cannot - * use java.util.function.Predicate because that is not present on all API - * versions that this test must run on. - */ - private interface Predicate { - boolean apply(T t); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java deleted file mode 100644 index 8b8604365fa1..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE; -import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY; - -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.GraphicBuffer; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.UserManager; -import android.support.test.InstrumentationRegistry; - -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; - -import java.io.File; - -/** - * Base class for tests that use a {@link TaskSnapshotPersister}. - */ -class TaskSnapshotPersisterTestBase extends WindowTestsBase { - - private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40); - - TaskSnapshotPersister mPersister; - TaskSnapshotLoader mLoader; - int mTestUserId; - static File sFilesDir; - - @BeforeClass - public static void setUpUser() { - sFilesDir = InstrumentationRegistry.getContext().getFilesDir(); - } - - @Before - public void setUp() throws Exception { - super.setUp(); - final UserManager um = UserManager.get(InstrumentationRegistry.getContext()); - mTestUserId = um.getUserHandle(); - mPersister = new TaskSnapshotPersister(userId -> sFilesDir); - mLoader = new TaskSnapshotLoader(mPersister); - mPersister.start(); - } - - @After - public void tearDown() throws Exception { - cleanDirectory(); - } - - private void cleanDirectory() { - final File[] files = new File(sFilesDir, "snapshots").listFiles(); - if (files == null) { - return; - } - for (File file : files) { - if (!file.isDirectory()) { - file.delete(); - } - } - } - - TaskSnapshot createSnapshot() { - return createSnapshot(1f /* scale */); - } - - TaskSnapshot createSnapshot(float scale) { - return new TaskSnapshotBuilder() - .setScale(scale) - .build(); - } - - /** - * Builds a TaskSnapshot. - */ - class TaskSnapshotBuilder { - - private float mScale = 1f; - private boolean mIsRealSnapshot = true; - private boolean mIsTranslucent = false; - private int mWindowingMode = WINDOWING_MODE_FULLSCREEN; - private int mSystemUiVisibility = 0; - - public TaskSnapshotBuilder setScale(float scale) { - mScale = scale; - return this; - } - - public TaskSnapshotBuilder setIsRealSnapshot(boolean isRealSnapshot) { - mIsRealSnapshot = isRealSnapshot; - return this; - } - - public TaskSnapshotBuilder setIsTranslucent(boolean isTranslucent) { - mIsTranslucent = isTranslucent; - return this; - } - - public TaskSnapshotBuilder setWindowingMode(int windowingMode) { - mWindowingMode = windowingMode; - return this; - } - - public TaskSnapshotBuilder setSystemUiVisibility(int systemUiVisibility) { - mSystemUiVisibility = systemUiVisibility; - return this; - } - - public TaskSnapshot build() { - final GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888, - USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY); - Canvas c = buffer.lockCanvas(); - c.drawColor(Color.RED); - buffer.unlockCanvasAndPost(c); - return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS, - mScale < 1f /* reducedResolution */, mScale, mIsRealSnapshot, mWindowingMode, - mSystemUiVisibility, mIsTranslucent); - } - - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java deleted file mode 100644 index b19373efd1b0..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager.TaskSnapshot; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.GraphicBuffer; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.Surface; - -import com.android.server.wm.TaskSnapshotSurface.Window; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link TaskSnapshotSurface}. - * - * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskSnapshotSurfaceTest extends WindowTestsBase { - - private TaskSnapshotSurface mSurface; - - private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis, - int windowFlags, Rect taskBounds) { - final GraphicBuffer buffer = GraphicBuffer.create(width, height, PixelFormat.RGBA_8888, - GraphicBuffer.USAGE_SW_READ_NEVER | GraphicBuffer.USAGE_SW_WRITE_NEVER); - final TaskSnapshot snapshot = new TaskSnapshot(buffer, - ORIENTATION_PORTRAIT, contentInsets, false, 1.0f, true /* isRealSnapshot */, - WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */); - mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test", - Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds, - ORIENTATION_PORTRAIT); - } - - private void setupSurface(int width, int height) { - setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, width, height)); - } - - @Test - public void fillEmptyBackground_fillHorizontally() throws Exception { - setupSurface(200, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - } - - @Test - public void fillEmptyBackground_fillVertically() throws Exception { - setupSurface(100, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(200); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_fillBoth() throws Exception { - setupSurface(200, 200); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(200); - when(mockCanvas.getHeight()).thenReturn(200); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); - verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); - } - - @Test - public void fillEmptyBackground_dontFill_sameSize() throws Exception { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception { - setupSurface(100, 100); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testCalculateSnapshotCrop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 100, 90), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_taskNotOnTop() { - setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 100)); - assertEquals(new Rect(0, 10, 100, 90), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_navBarLeft() { - setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(10, 0, 100, 100), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotCrop_navBarRight() { - setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100)); - assertEquals(new Rect(0, 0, 90, 100), mSurface.calculateSnapshotCrop()); - } - - @Test - public void testCalculateSnapshotFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - assertEquals(new Rect(0, -10, 100, 70), - mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); - } - - @Test - public void testCalculateSnapshotFrame_navBarLeft() { - setupSurface(100, 100); - final Rect insets = new Rect(10, 10, 0, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - assertEquals(new Rect(0, -10, 90, 80), - mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); - } - - @Test - public void testDrawStatusBarBackground() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground( - mockCanvas, new Rect(0, 0, 50, 100), 10); - verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); - } - - @Test - public void testDrawStatusBarBackground_nullFrame() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground( - mockCanvas, null, 10); - verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); - } - - @Test - public void testDrawStatusBarBackground_nope() { - setupSurface(100, 100); - final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground( - mockCanvas, new Rect(0, 0, 100, 100), 10); - verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); - } - - @Test - public void testDrawNavigationBarBackground() { - final Rect insets = new Rect(0, 10, 0, 10); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); - } - - @Test - public void testDrawNavigationBarBackground_left() { - final Rect insets = new Rect(10, 10, 0, 0); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); - } - - @Test - public void testDrawNavigationBarBackground_right() { - final Rect insets = new Rect(0, 10, 10, 0); - setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); - final Canvas mockCanvas = mock(Canvas.class); - when(mockCanvas.getWidth()).thenReturn(100); - when(mockCanvas.getHeight()).thenReturn(100); - mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas); - verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java deleted file mode 100644 index ca1994f85ccf..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.Before; -import org.junit.After; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -/** - * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.TaskStackContainersTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskStackContainersTests extends WindowTestsBase { - - private TaskStack mPinnedStack; - - @Before - public void setUp() throws Exception { - super.setUp(); - mPinnedStack = createStackControllerOnStackOnDisplay( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; - // Stack should contain visible app window to be considered visible. - final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */); - assertFalse(mPinnedStack.isVisible()); - final WindowTestUtils.TestAppWindowToken pinnedApp = - WindowTestUtils.createTestAppWindowToken(mDisplayContent); - pinnedTask.addChild(pinnedApp, 0 /* addPos */); - assertTrue(mPinnedStack.isVisible()); - } - - @After - public void tearDown() throws Exception { - mPinnedStack.removeImmediately(); - } - - @Test - public void testStackPositionChildAt() throws Exception { - // Test that always-on-top stack can't be moved to position other than top. - final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent); - final TaskStack stack2 = createTaskStackOnDisplay(mDisplayContent); - - final WindowContainer taskStackContainer = stack1.getParent(); - - final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1); - final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack); - assertGreaterThan(pinnedStackPos, stack2Pos); - assertGreaterThan(stack2Pos, stack1Pos); - - taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false); - assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); - assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); - - taskStackContainer.positionChildAt(1, mPinnedStack, false); - assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); - assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); - } - - @Test - public void testStackPositionBelowPinnedStack() throws Exception { - // Test that no stack can be above pinned stack. - final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent); - - final WindowContainer taskStackContainer = stack1.getParent(); - - final int stackPos = taskStackContainer.mChildren.indexOf(stack1); - final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack); - assertGreaterThan(pinnedStackPos, stackPos); - - taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false); - assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); - - taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false); - assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); - assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java deleted file mode 100644 index eaf71f0dc5b3..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Tests for the {@link TaskStack} class. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.TaskStackTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskStackTests extends WindowTestsBase { - - @Test - public void testStackPositionChildAt() throws Exception { - final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); - final Task task1 = createTaskInStack(stack, 0 /* userId */); - final Task task2 = createTaskInStack(stack, 1 /* userId */); - - // Current user task should be moved to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */); - assertEquals(stack.mChildren.get(0), task2); - assertEquals(stack.mChildren.get(1), task1); - - // Non-current user won't be moved to top. - stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */); - assertEquals(stack.mChildren.get(0), task2); - assertEquals(stack.mChildren.get(1), task1); - } - - @Test - public void testClosingAppDifferentStackOrientation() throws Exception { - final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); - final Task task1 = createTaskInStack(stack, 0 /* userId */); - WindowTestUtils.TestAppWindowToken appWindowToken1 = - WindowTestUtils.createTestAppWindowToken(mDisplayContent); - task1.addChild(appWindowToken1, 0); - appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - final Task task2 = createTaskInStack(stack, 1 /* userId */); - WindowTestUtils.TestAppWindowToken appWindowToken2 = - WindowTestUtils.createTestAppWindowToken(mDisplayContent); - task2.addChild(appWindowToken2, 0); - appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation()); - sWm.mClosingApps.add(appWindowToken2); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation()); - } - - @Test - public void testMoveTaskToBackDifferentStackOrientation() throws Exception { - final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); - final Task task1 = createTaskInStack(stack, 0 /* userId */); - WindowTestUtils.TestAppWindowToken appWindowToken1 = - WindowTestUtils.createTestAppWindowToken(mDisplayContent); - task1.addChild(appWindowToken1, 0); - appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - - final Task task2 = createTaskInStack(stack, 1 /* userId */); - WindowTestUtils.TestAppWindowToken appWindowToken2 = - WindowTestUtils.createTestAppWindowToken(mDisplayContent); - task2.addChild(appWindowToken2, 0); - appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); - - assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation()); - task2.setSendingToBottom(true); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation()); - } - - @Test - public void testStackRemoveImmediately() throws Exception { - final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); - assertEquals(stack, task.mStack); - - // Remove stack and check if its child is also removed. - stack.removeImmediately(); - assertNull(stack.getDisplayContent()); - assertNull(task.mStack); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java deleted file mode 100644 index 1dd9365825cf..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Test class for {@link TaskWindowContainerController}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.TaskWindowContainerControllerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class TaskWindowContainerControllerTests extends WindowTestsBase { - - @Test - public void testRemoveContainer() throws Exception { - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController appController = - new WindowTestUtils.TestAppWindowContainerController(taskController); - - taskController.removeContainer(); - // Assert that the container was removed. - assertNull(taskController.mContainer); - assertNull(appController.mContainer); - } - - @Test - public void testRemoveContainer_deferRemoval() throws Exception { - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(this); - final WindowTestUtils.TestAppWindowContainerController appController = - new WindowTestUtils.TestAppWindowContainerController(taskController); - - final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; - final AppWindowToken app = appController.mContainer; - task.mShouldDeferRemoval = true; - - taskController.removeContainer(); - // For the case of deferred removal the task controller will no longer be connected to the - // container, but the app controller will still be connected to the its container until - // the task window container is removed. - assertNull(taskController.mContainer); - assertNull(task.getController()); - assertNotNull(appController.mContainer); - assertNotNull(app.getController()); - - task.removeImmediately(); - assertNull(appController.mContainer); - assertNull(app.getController()); - } - - @Test - public void testReparent() throws Exception { - final StackWindowController stackController1 = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stackController1); - final StackWindowController stackController2 = - createStackControllerOnDisplay(mDisplayContent); - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stackController2); - - boolean gotException = false; - try { - taskController.reparent(stackController1, 0, false/* moveParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue("Should not be able to reparent to the same parent", gotException); - - final StackWindowController stackController3 = - createStackControllerOnDisplay(mDisplayContent); - stackController3.setContainer(null); - gotException = false; - try { - taskController.reparent(stackController3, 0, false/* moveParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue("Should not be able to reparent to a stack that doesn't have a container", - gotException); - - taskController.reparent(stackController2, 0, false/* moveParents */); - assertEquals(stackController2.mContainer, taskController.mContainer.getParent()); - assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent()); - assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent()); - } - - @Test - public void testReparent_BetweenDisplays() throws Exception { - // Create first stack on primary display. - final StackWindowController stack1Controller = - createStackControllerOnDisplay(mDisplayContent); - final TaskStack stack1 = stack1Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController = - new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); - final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; - task1.mOnDisplayChangedCalled = false; - assertEquals(mDisplayContent, stack1.getDisplayContent()); - - // Create second display and put second stack on it. - final DisplayContent dc = createNewDisplay(); - final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); - final TaskStack stack2 = stack2Controller.mContainer; - final WindowTestUtils.TestTaskWindowContainerController taskController2 = - new WindowTestUtils.TestTaskWindowContainerController(stack2Controller); - final WindowTestUtils.TestTask task2 = - (WindowTestUtils.TestTask) taskController2.mContainer; - - // Reparent and check state - taskController.reparent(stack2Controller, 0, false /* moveParents */); - assertEquals(stack2, task1.getParent()); - assertEquals(0, task1.positionInParent()); - assertEquals(1, task2.positionInParent()); - assertTrue(task1.mOnDisplayChangedCalled); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java deleted file mode 100644 index 353aa7deb534..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import com.android.internal.os.IResultReceiver; - -import android.graphics.Rect; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.util.MergedConfiguration; -import android.view.DisplayCutout; -import android.view.DragEvent; -import android.view.IWindow; - -public class TestIWindow extends IWindow.Stub { - @Override - public void executeCommand(String command, String parameters, - ParcelFileDescriptor descriptor) - throws RemoteException { - - } - - @Override - public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, - Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfig, - Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId, - DisplayCutout.ParcelableWrapper displayCutout) - throws RemoteException { - - } - - @Override - public void moved(int newX, int newY) throws RemoteException { - - } - - @Override - public void dispatchAppVisibility(boolean visible) throws RemoteException { - - } - - @Override - public void dispatchGetNewSurface() throws RemoteException { - - } - - @Override - public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) - throws RemoteException { - - } - - @Override - public void closeSystemDialogs(String reason) throws RemoteException { - - } - - @Override - public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, - boolean sync) - throws RemoteException { - - } - - @Override - public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, - boolean sync) throws RemoteException { - - } - - @Override - public void dispatchDragEvent(DragEvent event) throws RemoteException { - - } - - @Override - public void updatePointerIcon(float x, float y) throws RemoteException { - - } - - @Override - public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, int localValue, - int localChanges) throws RemoteException { - - } - - @Override - public void dispatchWindowShown() throws RemoteException { - - } - - @Override - public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) - throws RemoteException { - - } - - @Override - public void dispatchPointerCaptureChanged(boolean hasCapture) { - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java deleted file mode 100644 index ee028ba4262b..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ /dev/null @@ -1,606 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; - -import static org.mockito.Mockito.mock; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.CompatibilityInfo; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.proto.ProtoOutputStream; -import android.view.Display; -import android.view.DisplayCutout; -import android.view.IWindow; -import android.view.IWindowManager; -import android.view.KeyEvent; -import android.view.WindowManager; -import android.view.animation.Animation; - -import com.android.internal.policy.IKeyguardDismissCallback; -import com.android.internal.policy.IShortcutService; -import com.android.server.policy.WindowManagerPolicy; - -import java.io.PrintWriter; -import java.util.function.Supplier; - -class TestWindowManagerPolicy implements WindowManagerPolicy { - private static final String TAG = "TestWindowManagerPolicy"; - - private final Supplier mWmSupplier; - - int rotationToReport = 0; - boolean keyguardShowingAndNotOccluded = false; - - private Runnable mRunnableWhenAddingSplashScreen; - - public TestWindowManagerPolicy(Supplier wmSupplier) { - - mWmSupplier = wmSupplier; - } - - @Override - public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver) - throws RemoteException { - - } - - @Override - public void init(Context context, IWindowManager windowManager, - WindowManagerFuncs windowManagerFuncs) { - - } - - @Override - public boolean isDefaultOrientationForced() { - return false; - } - - @Override - public void setInitialDisplaySize(Display display, int width, int height, int density) { - - } - - @Override - public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) { - return 0; - } - - @Override - public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs) { - return false; - } - - @Override - public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs, - boolean hasStatusBarServicePermission) { - } - - @Override - public void adjustConfigurationLw(Configuration config, int keyboardPresence, - int navigationPresence) { - - } - - @Override - public int getMaxWallpaperLayer() { - return 0; - } - - @Override - public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, - int displayId, DisplayCutout displayCutout) { - return 0; - } - - @Override - public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, - int displayId, DisplayCutout displayCutout) { - return 0; - } - - @Override - public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, - int displayId, DisplayCutout displayCutout) { - return 0; - } - - @Override - public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, - int displayId, DisplayCutout displayCutout) { - return 0; - } - - @Override - public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) { - return attrs.type == TYPE_STATUS_BAR; - } - - @Override - public boolean canBeHiddenByKeyguardLw(WindowState win) { - return false; - } - - /** - * Sets a runnable to run when adding a splash screen which gets executed after the window has - * been added but before returning the surface. - */ - void setRunnableWhenAddingSplashScreen(Runnable r) { - mRunnableWhenAddingSplashScreen = r; - } - - @Override - public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme, - CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, - int logo, int windowFlags, Configuration overrideConfig, int displayId) { - final com.android.server.wm.WindowState window; - final AppWindowToken atoken; - final WindowManagerService wm = mWmSupplier.get(); - synchronized (wm.mWindowMap) { - atoken = wm.mRoot.getAppWindowToken(appToken); - window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken, - "Starting window", 0 /* ownerId */, false /* internalWindows */, wm, - mock(Session.class), mock(IWindow.class)); - atoken.startingWindow = window; - } - if (mRunnableWhenAddingSplashScreen != null) { - mRunnableWhenAddingSplashScreen.run(); - mRunnableWhenAddingSplashScreen = null; - } - return () -> { - synchronized (wm.mWindowMap) { - atoken.removeChild(window); - atoken.startingWindow = null; - } - }; - } - - @Override - public int prepareAddWindowLw(WindowState win, - WindowManager.LayoutParams attrs) { - return 0; - } - - @Override - public void removeWindowLw(WindowState win) { - - } - - @Override - public int selectAnimationLw(WindowState win, int transit) { - return 0; - } - - @Override - public void selectRotationAnimationLw(int[] anim) { - - } - - @Override - public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId, - boolean forceDefault) { - return false; - } - - @Override - public Animation createHiddenByKeyguardExit(boolean onWallpaper, - boolean goingToNotificationShade) { - return null; - } - - @Override - public Animation createKeyguardWallpaperExit(boolean goingToNotificationShade) { - return null; - } - - @Override - public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { - return 0; - } - - @Override - public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { - return 0; - } - - @Override - public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, - int policyFlags) { - return 0; - } - - @Override - public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, - int policyFlags) { - return null; - } - - @Override - public int getSystemDecorLayerLw() { - return 0; - } - - @Override - public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) { - - } - - @Override - public void applyPostLayoutPolicyLw(WindowState win, - WindowManager.LayoutParams attrs, WindowState attached, WindowState imeTarget) { - } - - @Override - public int finishPostLayoutPolicyLw() { - return 0; - } - - @Override - public boolean allowAppAnimationsLw() { - return false; - } - - @Override - public int focusChangedLw(WindowState lastFocus, - WindowState newFocus) { - return 0; - } - - @Override - public void startedWakingUp() { - - } - - @Override - public void finishedWakingUp() { - - } - - @Override - public void startedGoingToSleep(int why) { - - } - - @Override - public void finishedGoingToSleep(int why) { - - } - - @Override - public void screenTurningOn(ScreenOnListener screenOnListener) { - - } - - @Override - public void screenTurnedOn() { - - } - - @Override - public void screenTurningOff(ScreenOffListener screenOffListener) { - - } - - @Override - public void screenTurnedOff() { - - } - - @Override - public boolean isScreenOn() { - return true; - } - - @Override - public boolean okToAnimate() { - return true; - } - - @Override - public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { - - } - - @Override - public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) { - - } - - @Override - public void enableKeyguard(boolean enabled) { - - } - - @Override - public void exitKeyguardSecurely(OnKeyguardExitResult callback) { - - } - - @Override - public boolean isKeyguardLocked() { - return keyguardShowingAndNotOccluded; - } - - @Override - public boolean isKeyguardSecure(int userId) { - return false; - } - - @Override - public boolean isKeyguardOccluded() { - return false; - } - - @Override - public boolean isKeyguardTrustedLw() { - return false; - } - - @Override - public boolean isKeyguardShowingAndNotOccluded() { - return keyguardShowingAndNotOccluded; - } - - @Override - public boolean inKeyguardRestrictedKeyInputMode() { - return false; - } - - @Override - public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback, - CharSequence message) { - } - - @Override - public boolean isKeyguardDrawnLw() { - return false; - } - - @Override - public boolean isShowingDreamLw() { - return false; - } - - @Override - public void onKeyguardOccludedChangedLw(boolean occluded) { - } - - @Override - public int rotationForOrientationLw(int orientation, int lastRotation, boolean defaultDisplay) { - return rotationToReport; - } - - @Override - public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation) { - return true; - } - - @Override - public void setRotationLw(int rotation) { - - } - - @Override - public void setSafeMode(boolean safeMode) { - - } - - @Override - public void systemReady() { - - } - - @Override - public void systemBooted() { - - } - - @Override - public void showBootMessage(CharSequence msg, boolean always) { - - } - - @Override - public void hideBootMessages() { - - } - - @Override - public void userActivity() { - - } - - @Override - public void enableScreenAfterBoot() { - - } - - @Override - public void setCurrentOrientationLw(int newOrientation) { - - } - - @Override - public boolean performHapticFeedbackLw(WindowState win, int effectId, - boolean always) { - return false; - } - - @Override - public void keepScreenOnStartedLw() { - - } - - @Override - public void keepScreenOnStoppedLw() { - - } - - @Override - public int getUserRotationMode() { - return 0; - } - - @Override - public void setUserRotationMode(int mode, - int rotation) { - - } - - @Override - public int adjustSystemUiVisibilityLw(int visibility) { - return 0; - } - - @Override - public boolean hasNavigationBar() { - return false; - } - - @Override - public void lockNow(Bundle options) { - - } - - @Override - public void setLastInputMethodWindowLw(WindowState ime, - WindowState target) { - - } - - @Override - public void showRecentApps() { - - } - - @Override - public void showGlobalActions() { - - } - - @Override - public void setCurrentUserLw(int newUserId) { - - } - - @Override - public void setSwitchingUser(boolean switching) { - - } - - @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { - - } - - @Override - public void dump(String prefix, PrintWriter writer, String[] args) { - - } - - @Override - public boolean isTopLevelWindow(int windowType) { - return false; - } - - @Override - public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { - - } - - @Override - public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, - DisplayCutout cutout, Rect outInsets) { - - } - - @Override - public boolean isNavBarForcedShownLw(WindowState win) { - return false; - } - - @NavigationBarPosition - @Override - public int getNavBarPosition() { - return NAV_BAR_BOTTOM; - } - - @Override - public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight, - DisplayCutout cutout, Rect outInsets) { - - } - - @Override - public boolean isDockSideAllowed(int dockSide, int originalDockSide, int displayWidth, - int displayHeight, int displayRotation) { - return false; - } - - @Override - public void onConfigurationChanged() { - - } - - @Override - public boolean shouldRotateSeamlessly(int oldRotation, int newRotation) { - return false; - } - - @Override - public void setPipVisibilityLw(boolean visible) { - - } - - @Override - public void setRecentsVisibilityLw(boolean visible) { - - } - - @Override - public void setNavBarVirtualKeyHapticFeedbackEnabledLw(boolean enabled) { - } - - @Override - public void onSystemUiStarted() { - } - - @Override - public boolean canDismissBootAnimation() { - return true; - } - - @Override - public void requestUserActivityNotification() { - } - - @Override - public void onLockTaskStateChangedLw(int lockTaskState) { - } - - @Override - public boolean setAodShowing(boolean aodShowing) { - return false; - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java deleted file mode 100644 index a5c47deb2c06..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static junit.framework.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link AppTransition}. - * - * runtest frameworks-services -c com.android.server.wm.UnknownAppVisibilityControllerTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class UnknownAppVisibilityControllerTest extends WindowTestsBase { - - @Before - public void setUp() throws Exception { - super.setUp(); - sWm.mUnknownAppVisibilityController.clear(); - } - - @Test - public void testFlow() throws Exception { - final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - sWm.mUnknownAppVisibilityController.notifyLaunched(token); - sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token); - sWm.mUnknownAppVisibilityController.notifyRelayouted(token); - - // Make sure our handler processed the message. - Thread.sleep(100); - assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); - } - - @Test - public void testMultiple() throws Exception { - final AppWindowToken token1 = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - final AppWindowToken token2 = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - sWm.mUnknownAppVisibilityController.notifyLaunched(token1); - sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token1); - sWm.mUnknownAppVisibilityController.notifyLaunched(token2); - sWm.mUnknownAppVisibilityController.notifyRelayouted(token1); - sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token2); - sWm.mUnknownAppVisibilityController.notifyRelayouted(token2); - - // Make sure our handler processed the message. - Thread.sleep(100); - assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); - } - - @Test - public void testClear() throws Exception { - final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - sWm.mUnknownAppVisibilityController.notifyLaunched(token); - sWm.mUnknownAppVisibilityController.clear();; - assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); - } - - @Test - public void testAppRemoved() throws Exception { - final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - sWm.mUnknownAppVisibilityController.notifyLaunched(token); - sWm.mUnknownAppVisibilityController.appRemovedOrHidden(token); - assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java deleted file mode 100644 index 71ead204c9df..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WallpaperControllerTests.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.android.server.wm; - -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; - -import static junit.framework.TestCase.assertNotNull; - -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.graphics.Bitmap; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link WallpaperController} class. - * - * Build/Install/Run: - * atest com.android.server.wm.WallpaperControllerTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WallpaperControllerTests extends WindowTestsBase { - @Test - public void testWallpaperScreenshot() { - WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class); - - synchronized (sWm.mWindowMap) { - // No wallpaper - final DisplayContent dc = createNewDisplay(); - Bitmap wallpaperBitmap = sWm.mRoot.mWallpaperController.screenshotWallpaperLocked(); - assertNull(wallpaperBitmap); - - // No wallpaper WSA Surface - WindowToken wallpaperWindowToken = new WallpaperWindowToken(sWm, mock(IBinder.class), - true, dc, true /* ownerCanManageAppTokens */); - WindowState wallpaperWindow = createWindow(null /* parent */, TYPE_WALLPAPER, - wallpaperWindowToken, "wallpaperWindow"); - wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); - assertNull(wallpaperBitmap); - - // Wallpaper with not visible WSA surface. - wallpaperWindow.mWinAnimator.mSurfaceController = windowSurfaceController; - wallpaperWindow.mWinAnimator.mLastAlpha = 1; - wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); - assertNull(wallpaperBitmap); - - when(windowSurfaceController.getShown()).thenReturn(true); - - // Wallpaper with WSA alpha set to 0. - wallpaperWindow.mWinAnimator.mLastAlpha = 0; - wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); - assertNull(wallpaperBitmap); - - // Wallpaper window with WSA Surface - wallpaperWindow.mWinAnimator.mLastAlpha = 1; - wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); - assertNotNull(wallpaperBitmap); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java deleted file mode 100644 index ca520ed76be6..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; - -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.graphics.Point; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.SurfaceControl; -import android.view.animation.Animation; -import android.view.animation.ClipRectAnimation; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link WindowAnimationSpec} class. - * - * atest FrameworksServicesTests:com.android.server.wm.WindowAnimationSpecTest - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowAnimationSpecTest { - private final SurfaceControl mSurfaceControl = mock(SurfaceControl.class); - private final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class); - private final Animation mAnimation = mock(Animation.class); - private final Rect mStackBounds = new Rect(0, 0, 10, 10); - - @Test - public void testApply_clipNone() { - Rect windowCrop = new Rect(0, 0, 20, 20); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(windowCrop))); - } - - @Test - public void testApply_clipAfter() { - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, - mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); - verify(mTransaction).setFinalCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(mStackBounds))); - } - - @Test - public void testApply_clipAfterOffsetPosition() { - // Stack bounds is (0, 0, 10, 10) position is (20, 40) - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, - new Point(20, 40), mStackBounds, false /* canSkipFirstFrame */, - STACK_CLIP_AFTER_ANIM, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); - verify(mTransaction).setFinalCrop(eq(mSurfaceControl), - argThat(rect -> rect.left == 20 && rect.top == 40 && rect.right == 30 - && rect.bottom == 50)); - } - - @Test - public void testApply_clipBeforeNoAnimationBounds() { - // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0) - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, - mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(mStackBounds))); - } - - @Test - public void testApply_clipBeforeNoStackBounds() { - // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20) - Rect windowCrop = new Rect(0, 0, 20, 20); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - a.initialize(0, 0, 0, 0); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); - } - - @Test - public void testApply_clipBeforeSmallerAnimationClip() { - // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5) - Rect windowCrop = new Rect(0, 0, 5, 5); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(windowCrop))); - } - - @Test - public void testApply_clipBeforeSmallerStackClip() { - // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20) - Rect windowCrop = new Rect(0, 0, 20, 20); - Animation a = createClipRectAnimation(windowCrop, windowCrop); - WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, - mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, - true /* isAppAnimation */); - windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); - verify(mTransaction).setWindowCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(mStackBounds))); - } - - private Animation createClipRectAnimation(Rect fromClip, Rect toClip) { - Animation a = new ClipRectAnimation(fromClip, toClip); - a.initialize(0, 0, 0, 0); - return a; - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java deleted file mode 100644 index 513c1ecda990..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import org.junit.Test; - -import android.app.WindowConfiguration; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.DisplayInfo; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; -import static android.app.WindowConfiguration.WINDOW_CONFIG_WINDOWING_MODE; -import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - -/** - * Test class to for {@link android.app.WindowConfiguration}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.WindowConfigurationTests - */ -@SmallTest -@FlakyTest(bugId = 74078662) -@Presubmit -@org.junit.runner.RunWith(AndroidJUnit4.class) -public class WindowConfigurationTests extends WindowTestsBase { - private Rect mParentBounds; - - @Override - public void setUp() throws Exception { - super.setUp(); - mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); - } - - /** Tests {@link android.app.WindowConfiguration#diff(WindowConfiguration, boolean)}. */ - @Test - public void testDiff() { - final Configuration config1 = new Configuration(); - final WindowConfiguration winConfig1 = config1.windowConfiguration; - final Configuration config2 = new Configuration(); - final WindowConfiguration winConfig2 = config2.windowConfiguration; - final Configuration config3 = new Configuration(); - final WindowConfiguration winConfig3 = config3.windowConfiguration; - - winConfig1.setAppBounds(0, 1, 1, 0); - winConfig2.setAppBounds(1, 2, 2, 1); - winConfig3.setAppBounds(winConfig1.getAppBounds()); - - - assertEquals(CONFIG_WINDOW_CONFIGURATION, config1.diff(config2)); - assertEquals(0, config1.diffPublicOnly(config2)); - assertEquals(WINDOW_CONFIG_APP_BOUNDS, - winConfig1.diff(winConfig2, false /* compareUndefined */)); - - winConfig2.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertEquals(WINDOW_CONFIG_APP_BOUNDS | WINDOW_CONFIG_WINDOWING_MODE, - winConfig1.diff(winConfig2, false /* compareUndefined */)); - - assertEquals(0, config1.diff(config3)); - assertEquals(0, config1.diffPublicOnly(config3)); - assertEquals(0, winConfig1.diff(winConfig3, false /* compareUndefined */)); - } - - /** Tests {@link android.app.WindowConfiguration#compareTo(WindowConfiguration)}. */ - @Test - public void testConfigurationCompareTo() throws Exception { - final Configuration blankConfig = new Configuration(); - final WindowConfiguration blankWinConfig = new WindowConfiguration(); - - final Configuration config1 = new Configuration(); - final WindowConfiguration winConfig1 = config1.windowConfiguration; - winConfig1.setAppBounds(1, 2, 3, 4); - - final Configuration config2 = new Configuration(config1); - final WindowConfiguration winConfig2 = config2.windowConfiguration; - - assertEquals(config1.compareTo(config2), 0); - assertEquals(winConfig1.compareTo(winConfig2), 0); - - // Different windowing mode - winConfig2.setWindowingMode(WINDOWING_MODE_FREEFORM); - assertNotEquals(config1.compareTo(config2), 0); - assertNotEquals(winConfig1.compareTo(winConfig2), 0); - winConfig2.setWindowingMode(winConfig1.getWindowingMode()); - - // Different bounds - winConfig2.setAppBounds(0, 2, 3, 4); - assertNotEquals(config1.compareTo(config2), 0); - assertNotEquals(winConfig1.compareTo(winConfig2), 0); - - // No bounds - assertEquals(config1.compareTo(blankConfig), -1); - assertEquals(winConfig1.compareTo(blankWinConfig), -1); - - assertEquals(blankConfig.compareTo(config1), 1); - assertEquals(blankWinConfig.compareTo(winConfig1), 1); - } - - @Test - public void testSetActivityType() throws Exception { - final WindowConfiguration config = new WindowConfiguration(); - config.setActivityType(ACTIVITY_TYPE_HOME); - assertEquals(ACTIVITY_TYPE_HOME, config.getActivityType()); - - // Allowed to change from app process. - config.setActivityType(ACTIVITY_TYPE_STANDARD); - assertEquals(ACTIVITY_TYPE_STANDARD, config.getActivityType()); - } - - /** Ensures the configuration app bounds at the root level match the app dimensions. */ - @Test - public void testAppBounds_RootConfigurationBounds() throws Exception { - final DisplayInfo info = mDisplayContent.getDisplayInfo(); - info.appWidth = 1024; - info.appHeight = 768; - - final Rect appBounds = sWm.computeNewConfiguration( - mDisplayContent.getDisplayId()).windowConfiguration.getAppBounds(); - // The bounds should always be positioned in the top left. - assertEquals(appBounds.left, 0); - assertEquals(appBounds.top, 0); - - // The bounds should equal the defined app width and height - assertEquals(appBounds.width(), info.appWidth); - assertEquals(appBounds.height(), info.appHeight); - } - - /** Ensures that bounds are clipped to their parent. */ - @Test - public void testAppBounds_BoundsClipping() throws Exception { - final Rect shiftedBounds = new Rect(mParentBounds); - shiftedBounds.offset(10, 10); - final Rect expectedBounds = new Rect(mParentBounds); - expectedBounds.intersect(shiftedBounds); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds, - expectedBounds); - } - - /** Ensures that empty bounds are not propagated to the configuration. */ - @Test - public void testAppBounds_EmptyBounds() throws Exception { - final Rect emptyBounds = new Rect(); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, - null /*ExpectedBounds*/); - } - - /** Ensures that bounds on freeform stacks are not clipped. */ - @Test - public void testAppBounds_FreeFormBounds() throws Exception { - final Rect freeFormBounds = new Rect(mParentBounds); - freeFormBounds.offset(10, 10); - testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, - freeFormBounds); - } - - /** Ensures that fully contained bounds are not clipped. */ - @Test - public void testAppBounds_ContainedBounds() throws Exception { - final Rect insetBounds = new Rect(mParentBounds); - insetBounds.inset(5, 5, 5, 5); - testStackBoundsConfiguration( - WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds); - } - - /** Ensures that full screen free form bounds are clipped */ - @Test - public void testAppBounds_FullScreenFreeFormBounds() throws Exception { - final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth, - mDisplayInfo.logicalHeight); - testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds, - mParentBounds); - } - - private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, - Rect expectedConfigBounds) { - final StackWindowController stackController = createStackControllerOnStackOnDisplay( - windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent); - - final Configuration parentConfig = mDisplayContent.getConfiguration(); - parentConfig.windowConfiguration.setAppBounds(parentBounds); - - final Configuration config = new Configuration(); - final WindowConfiguration winConfig = config.windowConfiguration; - stackController.adjustConfigurationForBounds(bounds, null /*insetBounds*/, - new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/, - false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig, - windowingMode); - // Assert that both expected and actual are null or are equal to each other - - assertEquals(expectedConfigBounds, winConfig.getAppBounds()); - } - -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java deleted file mode 100644 index 502cb6e27c68..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerControllerTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.app.WindowConfiguration; -import android.content.res.Configuration; -import android.support.test.filters.FlakyTest; -import org.junit.Test; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.content.res.Configuration.EMPTY; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Test class for {@link WindowContainerController}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.WindowContainerControllerTests - */ -@SmallTest -@Presubmit -@FlakyTest(bugId = 74078662) -@org.junit.runner.RunWith(AndroidJUnit4.class) -public class WindowContainerControllerTests extends WindowTestsBase { - - @Test - public void testCreation() throws Exception { - final WindowContainerController controller = new WindowContainerController(null, sWm); - final WindowContainer container = new WindowContainer(sWm); - - container.setController(controller); - assertEquals(controller, container.getController()); - assertEquals(controller.mContainer, container); - } - - @Test - public void testSetContainer() throws Exception { - final WindowContainerController controller = new WindowContainerController(null, sWm); - final WindowContainer container = new WindowContainer(sWm); - - controller.setContainer(container); - assertEquals(controller.mContainer, container); - - // Assert we can't change the container to another one once set - boolean gotException = false; - try { - controller.setContainer(new WindowContainer(sWm)); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - - // Assert that we can set the container to null. - controller.setContainer(null); - assertNull(controller.mContainer); - } - - @Test - public void testRemoveContainer() throws Exception { - final WindowContainerController controller = new WindowContainerController(null, sWm); - final WindowContainer container = new WindowContainer(sWm); - - controller.setContainer(container); - assertEquals(controller.mContainer, container); - - controller.removeContainer(); - assertNull(controller.mContainer); - } - - @Test - public void testOnOverrideConfigurationChanged() throws Exception { - final WindowContainerController controller = new WindowContainerController(null, sWm); - final WindowContainer container = new WindowContainer(sWm); - - controller.setContainer(container); - assertEquals(controller.mContainer, container); - assertEquals(EMPTY, container.getOverrideConfiguration()); - - final Configuration config = new Configuration(); - config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - config.windowConfiguration.setAppBounds(10, 10, 10, 10); - - // Assert that the config change through the controller is propagated to the container. - controller.onOverrideConfigurationChanged(config); - assertEquals(config, container.getOverrideConfiguration()); - - // Assert the container configuration isn't changed after removal from the controller. - controller.removeContainer(); - controller.onOverrideConfigurationChanged(EMPTY); - assertEquals(config, container.getOverrideConfiguration()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java deleted file mode 100644 index 6c7830e5cf79..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java +++ /dev/null @@ -1,872 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.support.test.filters.FlakyTest; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.content.res.Configuration; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import java.util.Comparator; - -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - -import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; -import static com.android.server.wm.WindowContainer.POSITION_TOP; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyFloat; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Test class for {@link WindowContainer}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:com.android.server.wm.WindowContainerTests - */ -@SmallTest -@Presubmit -@FlakyTest(bugId = 74078662) -@RunWith(AndroidJUnit4.class) -public class WindowContainerTests extends WindowTestsBase { - - @Test - public void testCreation() throws Exception { - final TestWindowContainer w = new TestWindowContainerBuilder().setLayer(0).build(); - assertNull("window must have no parent", w.getParentWindow()); - assertEquals("window must have no children", 0, w.getChildrenCount()); - } - - @Test - public void testAdd() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer layer1 = root.addChildWindow(builder.setLayer(1)); - final TestWindowContainer secondLayer1 = root.addChildWindow(builder.setLayer(1)); - final TestWindowContainer layer2 = root.addChildWindow(builder.setLayer(2)); - final TestWindowContainer layerNeg1 = root.addChildWindow(builder.setLayer(-1)); - final TestWindowContainer layerNeg2 = root.addChildWindow(builder.setLayer(-2)); - final TestWindowContainer secondLayerNeg1 = root.addChildWindow(builder.setLayer(-1)); - final TestWindowContainer layer0 = root.addChildWindow(builder.setLayer(0)); - - assertEquals(7, root.getChildrenCount()); - - assertEquals(root, layer1.getParentWindow()); - assertEquals(root, secondLayer1.getParentWindow()); - assertEquals(root, layer2.getParentWindow()); - assertEquals(root, layerNeg1.getParentWindow()); - assertEquals(root, layerNeg2.getParentWindow()); - assertEquals(root, secondLayerNeg1.getParentWindow()); - assertEquals(root, layer0.getParentWindow()); - - assertEquals(layerNeg2, root.getChildAt(0)); - assertEquals(secondLayerNeg1, root.getChildAt(1)); - assertEquals(layerNeg1, root.getChildAt(2)); - assertEquals(layer0, root.getChildAt(3)); - assertEquals(layer1, root.getChildAt(4)); - assertEquals(secondLayer1, root.getChildAt(5)); - assertEquals(layer2, root.getChildAt(6)); - - assertTrue(layer1.mOnParentSetCalled); - assertTrue(secondLayer1.mOnParentSetCalled); - assertTrue(layer2.mOnParentSetCalled); - assertTrue(layerNeg1.mOnParentSetCalled); - assertTrue(layerNeg2.mOnParentSetCalled); - assertTrue(secondLayerNeg1.mOnParentSetCalled); - assertTrue(layer0.mOnParentSetCalled); - } - - @Test - public void testAddChildSetsSurfacePosition() throws Exception { - MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(); - - final SurfaceControl.Transaction transaction = mock(SurfaceControl.Transaction.class); - sWm.mTransactionFactory = () -> transaction; - - WindowContainer child = new WindowContainer(sWm); - child.setBounds(1, 1, 10, 10); - - verify(transaction, never()).setPosition(any(), anyFloat(), anyFloat()); - top.addChild(child, 0); - verify(transaction, times(1)).setPosition(any(), eq(1.f), eq(1.f)); - } - - @Test - public void testAdd_AlreadyHasParent() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - - boolean gotException = false; - try { - child1.addChildWindow(child2); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - - gotException = false; - try { - root.addChildWindow(child2); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - } - - @Test - public void testHasChild() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(); - final TestWindowContainer child21 = child2.addChildWindow(); - - assertEquals(2, root.getChildrenCount()); - assertEquals(2, child1.getChildrenCount()); - assertEquals(1, child2.getChildrenCount()); - - assertTrue(root.hasChild(child1)); - assertTrue(root.hasChild(child2)); - assertTrue(root.hasChild(child11)); - assertTrue(root.hasChild(child12)); - assertTrue(root.hasChild(child21)); - - assertTrue(child1.hasChild(child11)); - assertTrue(child1.hasChild(child12)); - assertFalse(child1.hasChild(child21)); - - assertTrue(child2.hasChild(child21)); - assertFalse(child2.hasChild(child11)); - assertFalse(child2.hasChild(child12)); - } - - @Test - public void testRemoveImmediately() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(); - final TestWindowContainer child21 = child2.addChildWindow(); - - assertNotNull(child12.getParentWindow()); - child12.removeImmediately(); - assertNull(child12.getParentWindow()); - assertEquals(1, child1.getChildrenCount()); - assertFalse(child1.hasChild(child12)); - assertFalse(root.hasChild(child12)); - - assertTrue(root.hasChild(child2)); - assertNotNull(child2.getParentWindow()); - child2.removeImmediately(); - assertNull(child2.getParentWindow()); - assertNull(child21.getParentWindow()); - assertEquals(0, child2.getChildrenCount()); - assertEquals(1, root.getChildrenCount()); - assertFalse(root.hasChild(child2)); - assertFalse(root.hasChild(child21)); - - assertTrue(root.hasChild(child1)); - assertTrue(root.hasChild(child11)); - - root.removeImmediately(); - assertEquals(0, root.getChildrenCount()); - } - - @Test - public void testRemoveImmediately_WithController() throws Exception { - final WindowContainer container = new WindowContainer(sWm); - final WindowContainerController controller = new WindowContainerController(null, sWm); - - container.setController(controller); - assertEquals(controller, container.getController()); - assertEquals(container, controller.mContainer); - - container.removeImmediately(); - assertNull(container.getController()); - assertNull(controller.mContainer); - } - - @Test - public void testSetController() throws Exception { - final WindowContainerController controller = new WindowContainerController(null, sWm); - final WindowContainer container = new WindowContainer(sWm); - - container.setController(controller); - assertEquals(controller, container.getController()); - assertEquals(container, controller.mContainer); - - // Assert we can't change the controller to another one once set - boolean gotException = false; - try { - container.setController(new WindowContainerController(null, sWm)); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - - // Assert that we can set the controller to null. - container.setController(null); - assertNull(container.getController()); - assertNull(controller.mContainer); - } - - @Test - public void testPositionChildAt() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child3 = root.addChildWindow(); - - // Test position at top. - root.positionChildAt(POSITION_TOP, child1, false /* includingParents */); - assertEquals(child1, root.getChildAt(root.getChildrenCount() - 1)); - - // Test position at bottom. - root.positionChildAt(POSITION_BOTTOM, child1, false /* includingParents */); - assertEquals(child1, root.getChildAt(0)); - - // Test position in the middle. - root.positionChildAt(1, child3, false /* includingParents */); - assertEquals(child1, root.getChildAt(0)); - assertEquals(child3, root.getChildAt(1)); - assertEquals(child2, root.getChildAt(2)); - } - - @Test - public void testPositionChildAtIncludeParents() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(); - final TestWindowContainer child13 = child1.addChildWindow(); - final TestWindowContainer child21 = child2.addChildWindow(); - final TestWindowContainer child22 = child2.addChildWindow(); - final TestWindowContainer child23 = child2.addChildWindow(); - - // Test moving to top. - child1.positionChildAt(POSITION_TOP, child11, true /* includingParents */); - assertEquals(child12, child1.getChildAt(0)); - assertEquals(child13, child1.getChildAt(1)); - assertEquals(child11, child1.getChildAt(2)); - assertEquals(child2, root.getChildAt(0)); - assertEquals(child1, root.getChildAt(1)); - - // Test moving to bottom. - child1.positionChildAt(POSITION_BOTTOM, child11, true /* includingParents */); - assertEquals(child11, child1.getChildAt(0)); - assertEquals(child12, child1.getChildAt(1)); - assertEquals(child13, child1.getChildAt(2)); - assertEquals(child1, root.getChildAt(0)); - assertEquals(child2, root.getChildAt(1)); - - // Test moving to middle, includeParents shouldn't do anything. - child2.positionChildAt(1, child21, true /* includingParents */); - assertEquals(child11, child1.getChildAt(0)); - assertEquals(child12, child1.getChildAt(1)); - assertEquals(child13, child1.getChildAt(2)); - assertEquals(child22, child2.getChildAt(0)); - assertEquals(child21, child2.getChildAt(1)); - assertEquals(child23, child2.getChildAt(2)); - assertEquals(child1, root.getChildAt(0)); - assertEquals(child2, root.getChildAt(1)); - } - - @Test - public void testPositionChildAtInvalid() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - - boolean gotException = false; - try { - // Check response to negative position. - root.positionChildAt(-1, child1, false /* includingParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - - gotException = false; - try { - // Check response to position that's bigger than child number. - root.positionChildAt(3, child1, false /* includingParents */); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - } - - @Test - public void testIsAnimating() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(builder.setIsAnimating(true)); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(builder.setIsAnimating(true)); - final TestWindowContainer child21 = child2.addChildWindow(); - - assertFalse(root.isAnimating()); - assertTrue(child1.isAnimating()); - assertTrue(child11.isAnimating()); - assertTrue(child12.isAnimating()); - assertFalse(child2.isAnimating()); - assertFalse(child21.isAnimating()); - - assertTrue(root.isSelfOrChildAnimating()); - assertTrue(child1.isSelfOrChildAnimating()); - assertFalse(child11.isSelfOrChildAnimating()); - assertTrue(child12.isSelfOrChildAnimating()); - assertFalse(child2.isSelfOrChildAnimating()); - assertFalse(child21.isSelfOrChildAnimating()); - } - - @Test - public void testIsVisible() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(builder.setIsVisible(true)); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(builder.setIsVisible(true)); - final TestWindowContainer child21 = child2.addChildWindow(); - - assertFalse(root.isVisible()); - assertTrue(child1.isVisible()); - assertFalse(child11.isVisible()); - assertTrue(child12.isVisible()); - assertFalse(child2.isVisible()); - assertFalse(child21.isVisible()); - } - - @Test - public void testOverrideConfigurationAncestorNotification() { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer grandparent = builder.setLayer(0).build(); - - final TestWindowContainer parent = grandparent.addChildWindow(); - final TestWindowContainer child = parent.addChildWindow(); - child.onOverrideConfigurationChanged(new Configuration()); - - assertTrue(grandparent.mOnDescendantOverrideCalled); - } - - @Test - public void testRemoveChild() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child21 = child2.addChildWindow(); - - assertTrue(root.hasChild(child2)); - assertTrue(root.hasChild(child21)); - root.removeChild(child2); - assertFalse(root.hasChild(child2)); - assertFalse(root.hasChild(child21)); - assertNull(child2.getParentWindow()); - - boolean gotException = false; - assertTrue(root.hasChild(child11)); - try { - // Can only detach our direct children. - root.removeChild(child11); - } catch (IllegalArgumentException e) { - gotException = true; - } - assertTrue(gotException); - } - - @Test - public void testGetOrientation_childSpecified() throws Exception { - testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_LANDSCAPE, - SCREEN_ORIENTATION_LANDSCAPE); - testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_UNSET, - SCREEN_ORIENTATION_UNSPECIFIED); - } - - private void testGetOrientation_childSpecifiedConfig(boolean childVisible, int childOrientation, - int expectedOrientation) { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - root.setFillsParent(true); - - builder.setIsVisible(childVisible); - - if (childOrientation != SCREEN_ORIENTATION_UNSET) { - builder.setOrientation(childOrientation); - } - - final TestWindowContainer child1 = root.addChildWindow(builder); - child1.setFillsParent(true); - - assertEquals(expectedOrientation, root.getOrientation()); - } - - @Test - public void testGetOrientation_Unset() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); - // Unspecified well because we didn't specify anything... - assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation()); - } - - @Test - public void testGetOrientation_InvisibleParentUnsetVisibleChildren() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); - - builder.setIsVisible(false).setLayer(-1); - final TestWindowContainer invisible = root.addChildWindow(builder); - builder.setIsVisible(true).setLayer(-2); - final TestWindowContainer invisibleChild1VisibleAndSet = invisible.addChildWindow(builder); - invisibleChild1VisibleAndSet.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - // Landscape well because the container is visible and that is what we set on it above. - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisibleChild1VisibleAndSet.getOrientation()); - // Landscape because even though the container isn't visible it has a child that is - // specifying it can influence the orientation by being visible. - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisible.getOrientation()); - // Landscape because the grandchild is visible and therefore can participate. - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation()); - - builder.setIsVisible(true).setLayer(-3); - final TestWindowContainer visibleUnset = root.addChildWindow(builder); - visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET); - assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnset.getOrientation()); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation()); - - } - - @Test - public void testGetOrientation_setBehind() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); - - builder.setIsVisible(true).setLayer(-1); - final TestWindowContainer visibleUnset = root.addChildWindow(builder); - visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET); - - builder.setIsVisible(true).setLayer(-2); - final TestWindowContainer visibleUnsetChild1VisibleSetBehind = - visibleUnset.addChildWindow(builder); - visibleUnsetChild1VisibleSetBehind.setOrientation(SCREEN_ORIENTATION_BEHIND); - // Setting to visible behind will be used by the parents if there isn't another other - // container behind this one that has an orientation set. - assertEquals(SCREEN_ORIENTATION_BEHIND, - visibleUnsetChild1VisibleSetBehind.getOrientation()); - assertEquals(SCREEN_ORIENTATION_BEHIND, visibleUnset.getOrientation()); - assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation()); - } - - @Test - public void testGetOrientation_fillsParent() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); - - builder.setIsVisible(true).setLayer(-1); - final TestWindowContainer visibleUnset = root.addChildWindow(builder); - visibleUnset.setOrientation(SCREEN_ORIENTATION_BEHIND); - - builder.setLayer(1).setIsVisible(true); - final TestWindowContainer visibleUnspecifiedRootChild = root.addChildWindow(builder); - visibleUnspecifiedRootChild.setFillsParent(false); - visibleUnspecifiedRootChild.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); - // Unset because the child doesn't fill the parent. May as well be invisible... - assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation()); - // The parent uses whatever orientation is set behind this container since it doesn't fill - // the parent. - assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation()); - - // Test case of child filling its parent, but its parent isn't filling its own parent. - builder.setLayer(2).setIsVisible(true); - final TestWindowContainer visibleUnspecifiedRootChildChildFillsParent = - visibleUnspecifiedRootChild.addChildWindow(builder); - visibleUnspecifiedRootChildChildFillsParent.setOrientation( - SCREEN_ORIENTATION_PORTRAIT); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, - visibleUnspecifiedRootChildChildFillsParent.getOrientation()); - assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation()); - assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation()); - - - visibleUnspecifiedRootChild.setFillsParent(true); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, visibleUnspecifiedRootChild.getOrientation()); - assertEquals(SCREEN_ORIENTATION_PORTRAIT, root.getOrientation()); - } - - @Test - public void testCompareTo() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.setLayer(0).build(); - - final TestWindowContainer child1 = root.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(); - - final TestWindowContainer child2 = root.addChildWindow(); - final TestWindowContainer child21 = child2.addChildWindow(); - final TestWindowContainer child22 = child2.addChildWindow(); - final TestWindowContainer child23 = child2.addChildWindow(); - final TestWindowContainer child221 = child22.addChildWindow(); - final TestWindowContainer child222 = child22.addChildWindow(); - final TestWindowContainer child223 = child22.addChildWindow(); - final TestWindowContainer child2221 = child222.addChildWindow(); - final TestWindowContainer child2222 = child222.addChildWindow(); - final TestWindowContainer child2223 = child222.addChildWindow(); - - final TestWindowContainer root2 = builder.setLayer(0).build(); - - assertEquals(0, root.compareTo(root)); - assertEquals(-1, child1.compareTo(child2)); - assertEquals(1, child2.compareTo(child1)); - - boolean inTheSameTree = true; - try { - root.compareTo(root2); - } catch (IllegalArgumentException e) { - inTheSameTree = false; - } - assertFalse(inTheSameTree); - - assertEquals(-1, child1.compareTo(child11)); - assertEquals(1, child21.compareTo(root)); - assertEquals(1, child21.compareTo(child12)); - assertEquals(-1, child11.compareTo(child2)); - assertEquals(1, child2221.compareTo(child11)); - assertEquals(-1, child2222.compareTo(child223)); - assertEquals(1, child2223.compareTo(child21)); - } - - @Test - public void testPrefixOrderIndex() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.build(); - - final TestWindowContainer child1 = root.addChildWindow(); - - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(); - - final TestWindowContainer child2 = root.addChildWindow(); - - final TestWindowContainer child21 = child2.addChildWindow(); - final TestWindowContainer child22 = child2.addChildWindow(); - - final TestWindowContainer child221 = child22.addChildWindow(); - final TestWindowContainer child222 = child22.addChildWindow(); - final TestWindowContainer child223 = child22.addChildWindow(); - - final TestWindowContainer child23 = child2.addChildWindow(); - - assertEquals(0, root.getPrefixOrderIndex()); - assertEquals(1, child1.getPrefixOrderIndex()); - assertEquals(2, child11.getPrefixOrderIndex()); - assertEquals(3, child12.getPrefixOrderIndex()); - assertEquals(4, child2.getPrefixOrderIndex()); - assertEquals(5, child21.getPrefixOrderIndex()); - assertEquals(6, child22.getPrefixOrderIndex()); - assertEquals(7, child221.getPrefixOrderIndex()); - assertEquals(8, child222.getPrefixOrderIndex()); - assertEquals(9, child223.getPrefixOrderIndex()); - assertEquals(10, child23.getPrefixOrderIndex()); - } - - @Test - public void testPrefixOrder_addEntireSubtree() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.build(); - final TestWindowContainer subtree = builder.build(); - final TestWindowContainer subtree2 = builder.build(); - - final TestWindowContainer child1 = subtree.addChildWindow(); - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child2 = subtree2.addChildWindow(); - final TestWindowContainer child3 = subtree2.addChildWindow(); - subtree.addChild(subtree2, 1); - root.addChild(subtree, 0); - - assertEquals(0, root.getPrefixOrderIndex()); - assertEquals(1, subtree.getPrefixOrderIndex()); - assertEquals(2, child1.getPrefixOrderIndex()); - assertEquals(3, child11.getPrefixOrderIndex()); - assertEquals(4, subtree2.getPrefixOrderIndex()); - assertEquals(5, child2.getPrefixOrderIndex()); - assertEquals(6, child3.getPrefixOrderIndex()); - } - - @Test - public void testPrefixOrder_remove() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.build(); - - final TestWindowContainer child1 = root.addChildWindow(); - - final TestWindowContainer child11 = child1.addChildWindow(); - final TestWindowContainer child12 = child1.addChildWindow(); - - final TestWindowContainer child2 = root.addChildWindow(); - - assertEquals(0, root.getPrefixOrderIndex()); - assertEquals(1, child1.getPrefixOrderIndex()); - assertEquals(2, child11.getPrefixOrderIndex()); - assertEquals(3, child12.getPrefixOrderIndex()); - assertEquals(4, child2.getPrefixOrderIndex()); - - root.removeChild(child1); - - assertEquals(1, child2.getPrefixOrderIndex()); - } - - /** - * Ensure children of a {@link WindowContainer} do not have - * {@link WindowContainer#onParentResize()} called when {@link WindowContainer#onParentResize()} - * is invoked with overridden bounds. - */ - @Test - public void testOnParentResizePropagation() throws Exception { - final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); - final TestWindowContainer root = builder.build(); - - final TestWindowContainer child = root.addChildWindow(); - child.setBounds(new Rect(1,1,2,2)); - - final TestWindowContainer grandChild = mock(TestWindowContainer.class); - - child.addChildWindow(grandChild); - root.onResize(); - - // Make sure the child does not propagate resize through onParentResize when bounds are set. - verify(grandChild, never()).onParentResize(); - - child.removeChild(grandChild); - - child.setBounds(null); - child.addChildWindow(grandChild); - root.onResize(); - - // Make sure the child propagates resize through onParentResize when no bounds set. - verify(grandChild, times(1)).onParentResize(); - } - - /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ - private class TestWindowContainer extends WindowContainer { - private final int mLayer; - private boolean mIsAnimating; - private boolean mIsVisible; - private boolean mFillsParent; - private Integer mOrientation; - - private boolean mOnParentSetCalled; - private boolean mOnDescendantOverrideCalled; - - /** - * Compares 2 window layers and returns -1 if the first is lesser than the second in terms - * of z-order and 1 otherwise. - */ - private final Comparator mWindowSubLayerComparator = (w1, w2) -> { - final int layer1 = w1.mLayer; - final int layer2 = w2.mLayer; - if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) { - // We insert the child window into the list ordered by the mLayer. For same layers, - // the negative one should go below others; the positive one should go above others. - return -1; - } - return 1; - }; - - TestWindowContainer(int layer, boolean isAnimating, boolean isVisible, - Integer orientation) { - super(sWm); - mLayer = layer; - mIsAnimating = isAnimating; - mIsVisible = isVisible; - mFillsParent = true; - mOrientation = orientation; - } - - TestWindowContainer getParentWindow() { - return (TestWindowContainer) getParent(); - } - - int getChildrenCount() { - return mChildren.size(); - } - - TestWindowContainer addChildWindow(TestWindowContainer child) { - addChild(child, mWindowSubLayerComparator); - return child; - } - - TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) { - TestWindowContainer child = childBuilder.build(); - addChild(child, mWindowSubLayerComparator); - return child; - } - - TestWindowContainer addChildWindow() { - return addChildWindow(new TestWindowContainerBuilder().setLayer(1)); - } - - @Override - void onParentSet() { - mOnParentSetCalled = true; - } - - @Override - void onDescendantOverrideConfigurationChanged() { - mOnDescendantOverrideCalled = true; - super.onDescendantOverrideConfigurationChanged(); - } - - @Override - boolean isSelfAnimating() { - return mIsAnimating; - } - - @Override - boolean isVisible() { - return mIsVisible; - } - - @Override - int getOrientation(int candidate) { - return mOrientation != null ? mOrientation : super.getOrientation(candidate); - } - - @Override - int getOrientation() { - return getOrientation(super.mOrientation); - } - - @Override - boolean fillsParent() { - return mFillsParent; - } - - void setFillsParent(boolean fillsParent) { - mFillsParent = fillsParent; - } - } - - private class TestWindowContainerBuilder { - private int mLayer; - private boolean mIsAnimating; - private boolean mIsVisible; - private Integer mOrientation; - - public TestWindowContainerBuilder() { - reset(); - } - - TestWindowContainerBuilder setLayer(int layer) { - mLayer = layer; - return this; - } - - TestWindowContainerBuilder setIsAnimating(boolean isAnimating) { - mIsAnimating = isAnimating; - return this; - } - - TestWindowContainerBuilder setIsVisible(boolean isVisible) { - mIsVisible = isVisible; - return this; - } - - TestWindowContainerBuilder setOrientation(int orientation) { - mOrientation = orientation; - return this; - } - - TestWindowContainerBuilder reset() { - mLayer = 0; - mIsAnimating = false; - mIsVisible = false; - mOrientation = null; - return this; - } - - TestWindowContainer build() { - return new TestWindowContainer(mLayer, mIsAnimating, mIsVisible, mOrientation); - } - } - - private class MockSurfaceBuildingContainer extends WindowContainer { - final SurfaceSession mSession = new SurfaceSession(); - - MockSurfaceBuildingContainer() { - super(sWm); - } - - class MockSurfaceBuilder extends SurfaceControl.Builder { - MockSurfaceBuilder(SurfaceSession ss) { - super(ss); - } - - @Override - public SurfaceControl build() { - return mock(SurfaceControl.class); - } - } - - @Override - SurfaceControl.Builder makeChildSurface(WindowContainer child) { - return new MockSurfaceBuilder(mSession); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java deleted file mode 100644 index e07639933b7a..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Matchers.eq; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.mockito.Mock; - -import java.util.function.Consumer; - -/** - * Tests for {@link WindowContainer#forAllWindows} and various implementations. - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowContainerTraversalTests extends WindowTestsBase { - - @Test - public void testDockedDividerPosition() throws Exception { - final WindowState splitScreenWindow = createWindowOnStack(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, - mDisplayContent, "splitScreenWindow"); - final WindowState splitScreenSecondaryWindow = createWindowOnStack(null, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, - TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); - - sWm.mInputMethodTarget = splitScreenWindow; - - Consumer c = mock(Consumer.class); - mDisplayContent.forAllWindows(c, false); - - verify(c).accept(eq(mDockedDividerWindow)); - verify(c).accept(eq(mImeWindow)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java deleted file mode 100644 index 5a563320f9cb..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.app.ActivityManager.TaskDescription; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.DisplayInfo; -import android.view.Gravity; -import android.view.IWindow; -import android.view.WindowManager; - -import static android.view.DisplayCutout.fromBoundingRect; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.FILL_PARENT; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.android.server.wm.utils.WmDisplayCutout; - -/** - * Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery. - * - * Build/Install/Run: bit FrameworksServicesTests:com.android.server.wm.WindowFrameTests - */ -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowFrameTests extends WindowTestsBase { - - private WindowToken mWindowToken; - private final IWindow mIWindow = new TestIWindow(); - - class WindowStateWithTask extends WindowState { - final Task mTask; - boolean mDockedResizingForTest = false; - WindowStateWithTask(WindowManager.LayoutParams attrs, Task t) { - super(sWm, null, mIWindow, mWindowToken, null, 0, 0, attrs, 0, 0, - false /* ownerCanAddInternalSystemWindow */); - mTask = t; - } - - @Override - Task getTask() { - return mTask; - } - - @Override - boolean isDockedResizing() { - return mDockedResizingForTest; - } - }; - - class TaskWithBounds extends Task { - final Rect mBounds; - final Rect mInsetBounds = new Rect(); - boolean mFullscreenForTest = true; - TaskWithBounds(Rect bounds) { - super(0, mStubStack, 0, sWm, 0, false, new TaskDescription(), null); - mBounds = bounds; - setBounds(bounds); - } - - @Override - public Rect getBounds() { - return mBounds; - } - - @Override - public void getBounds(Rect out) { - out.set(mBounds); - } - - @Override - public void getOverrideBounds(Rect outBounds) { - outBounds.set(mBounds); - } - @Override - void getTempInsetBounds(Rect outBounds) { - outBounds.set(mInsetBounds); - } - @Override - boolean isFullscreen() { - return mFullscreenForTest; - } - } - - TaskStack mStubStack; - - @Before - public void setUp() throws Exception { - super.setUp(); - - // Just any non zero value. - sWm.mSystemDecorLayer = 10000; - - mWindowToken = WindowTestUtils.createTestAppWindowToken( - sWm.getDefaultDisplayContentLocked()); - mStubStack = new TaskStack(sWm, 0, null); - } - - public void assertRect(Rect rect, int left, int top, int right, int bottom) { - assertEquals(left, rect.left); - assertEquals(top, rect.top); - assertEquals(right, rect.right); - assertEquals(bottom, rect.bottom); - } - - @Test - public void testLayoutInFullscreenTaskInsets() throws Exception { - Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame - WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final int bottomContentInset = 100; - final int topContentInset = 50; - final int bottomVisibleInset = 30; - final int topVisibleInset = 70; - final int leftStableInset = 20; - final int rightStableInset = 90; - - // With no insets or system decor all the frames incoming from PhoneWindowManager - // are identical. - final Rect pf = new Rect(0, 0, 1000, 1000); - final Rect df = pf; - final Rect of = df; - final Rect cf = new Rect(pf); - // Produce some insets - cf.top += 50; - cf.bottom -= 100; - final Rect vf = new Rect(pf); - vf.top += topVisibleInset; - vf.bottom -= bottomVisibleInset; - final Rect sf = new Rect(pf); - sf.left += leftStableInset; - sf.right -= rightStableInset; - - final Rect dcf = pf; - // When mFrame extends past cf, the content insets are - // the difference between mFrame and ContentFrame. Visible - // and stable frames work the same way. - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame,0, 0, 1000, 1000); - assertRect(w.mContentInsets, 0, topContentInset, 0, bottomContentInset); - assertRect(w.mVisibleInsets, 0, topVisibleInset, 0, bottomVisibleInset); - assertRect(w.mStableInsets, leftStableInset, 0, rightStableInset, 0); - // The frames remain as passed in shrunk to the window frame - assertTrue(cf.equals(w.getContentFrameLw())); - assertTrue(vf.equals(w.getVisibleFrameLw())); - assertTrue(sf.equals(w.getStableFrameLw())); - // On the other hand mFrame doesn't extend past cf we won't get any insets - w.mAttrs.x = 100; - w.mAttrs.y = 100; - w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT - w.mRequestedWidth = 100; - w.mRequestedHeight = 100; - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 100, 100, 200, 200); - assertRect(w.mContentInsets, 0, 0, 0, 0); - // In this case the frames are shrunk to the window frame. - assertTrue(w.mFrame.equals(w.getContentFrameLw())); - assertTrue(w.mFrame.equals(w.getVisibleFrameLw())); - assertTrue(w.mFrame.equals(w.getStableFrameLw())); - } - - @Test - public void testLayoutInFullscreenTaskNoInsets() throws Exception { - Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame - WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - // With no insets or system decor all the frames incoming from PhoneWindowManager - // are identical. - final Rect pf = new Rect(0, 0, 1000, 1000); - - // Here the window has FILL_PARENT, FILL_PARENT - // so we expect it to fill the entire available frame. - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 0, 0, 1000, 1000); - - // It can select various widths and heights within the bounds. - // Strangely the window attribute width is ignored for normal windows - // and we use mRequestedWidth/mRequestedHeight - w.mAttrs.width = 300; - w.mAttrs.height = 300; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - // Explicit width and height without requested width/height - // gets us nothing. - assertRect(w.mFrame, 0, 0, 0, 0); - - w.mRequestedWidth = 300; - w.mRequestedHeight = 300; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - // With requestedWidth/Height we can freely choose our size within the - // parent bounds. - assertRect(w.mFrame, 0, 0, 300, 300); - - // With FLAG_SCALED though, requestedWidth/height is used to control - // the unscaled surface size, and mAttrs.width/height becomes the - // layout controller. - w.mAttrs.flags = WindowManager.LayoutParams.FLAG_SCALED; - w.mRequestedHeight = -1; - w.mRequestedWidth = -1; - w.mAttrs.width = 100; - w.mAttrs.height = 100; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 0, 0, 100, 100); - w.mAttrs.flags = 0; - - // But sizes too large will be clipped to the containing frame - w.mRequestedWidth = 1200; - w.mRequestedHeight = 1200; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 0, 0, 1000, 1000); - - // Before they are clipped though windows will be shifted - w.mAttrs.x = 300; - w.mAttrs.y = 300; - w.mRequestedWidth = 1000; - w.mRequestedHeight = 1000; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 0, 0, 1000, 1000); - - // If there is room to move around in the parent frame the window will be shifted according - // to gravity. - w.mAttrs.x = 0; - w.mAttrs.y = 0; - w.mRequestedWidth = 300; - w.mRequestedHeight = 300; - w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 700, 0, 1000, 300); - w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 700, 700, 1000, 1000); - // Window specified x and y are interpreted as offsets in the opposite - // direction of gravity - w.mAttrs.x = 100; - w.mAttrs.y = 100; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, 600, 600, 900, 900); - } - - @Test - public void testLayoutNonfullscreenTask() { - final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo(); - final int logicalWidth = displayInfo.logicalWidth; - final int logicalHeight = displayInfo.logicalHeight; - - final int taskLeft = logicalWidth / 4; - final int taskTop = logicalHeight / 4; - final int taskRight = logicalWidth / 4 * 3; - final int taskBottom = logicalHeight / 4 * 3; - final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom); - TaskWithBounds task = new TaskWithBounds(taskBounds); - task.mFullscreenForTest = false; - WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, WmDisplayCutout.NO_CUTOUT, false); - // For non fullscreen tasks the containing frame is based off the - // task bounds not the parent frame. - assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); - assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom); - assertRect(w.mContentInsets, 0, 0, 0, 0); - - pf.set(0, 0, logicalWidth, logicalHeight); - // We still produce insets against the containing frame the same way. - final int cfRight = logicalWidth / 2; - final int cfBottom = logicalHeight / 2; - final Rect cf = new Rect(0, 0, cfRight, cfBottom); - w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); - int contentInsetRight = taskRight - cfRight; - int contentInsetBottom = taskBottom - cfBottom; - assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom); - assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight, - taskBottom - contentInsetBottom); - - pf.set(0, 0, logicalWidth, logicalHeight); - // However if we set temp inset bounds, the insets will be computed - // as if our window was laid out there, but it will be laid out according to - // the task bounds. - final int insetLeft = logicalWidth / 5; - final int insetTop = logicalHeight / 5; - final int insetRight = insetLeft + (taskRight - taskLeft); - final int insetBottom = insetTop + (taskBottom - taskTop); - task.mInsetBounds.set(insetLeft, insetTop, insetRight, insetBottom); - w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT, false); - assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); - contentInsetRight = insetRight - cfRight; - contentInsetBottom = insetBottom - cfBottom; - assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom); - assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight, - taskBottom - contentInsetBottom); - } - - @Test - public void testCalculatePolicyCrop() { - final WindowStateWithTask w = createWindow( - new TaskWithBounds(null), FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo(); - final int logicalWidth = displayInfo.logicalWidth; - final int logicalHeight = displayInfo.logicalHeight; - final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); - final Rect df = pf; - final Rect of = df; - final Rect cf = new Rect(pf); - // Produce some insets - cf.top += displayInfo.logicalWidth / 10; - cf.bottom -= displayInfo.logicalWidth / 5; - final Rect vf = cf; - final Rect sf = vf; - // We use a decor content frame with insets to produce cropping. - Rect dcf = cf; - - final Rect policyCrop = new Rect(); - - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); - w.calculatePolicyCrop(policyCrop); - assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom); - - dcf.setEmpty(); - // Likewise with no decor frame we would get no crop - w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); - w.calculatePolicyCrop(policyCrop); - assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight); - - // Now we set up a window which doesn't fill the entire decor frame. - // Normally it would be cropped to it's frame but in the case of docked resizing - // we need to account for the fact the windows surface will be made - // fullscreen and thus also make the crop fullscreen. - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - w.mAttrs.width = logicalWidth / 2; - w.mAttrs.height = logicalHeight / 2; - w.mRequestedWidth = logicalWidth / 2; - w.mRequestedHeight = logicalHeight / 2; - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); - - w.calculatePolicyCrop(policyCrop); - // Normally the crop is shrunk from the decor frame - // to the computed window frame. - assertRect(policyCrop, 0, 0, logicalWidth / 2, logicalHeight / 2); - - w.mDockedResizingForTest = true; - w.calculatePolicyCrop(policyCrop); - // But if we are docked resizing it won't be, however we will still be - // shrunk to the decor frame and the display. - assertRect(policyCrop, 0, 0, - Math.min(pf.width(), displayInfo.logicalWidth), - Math.min(pf.height(), displayInfo.logicalHeight)); - } - - @Test - public void testLayoutLetterboxedWindow() { - // First verify task behavior in multi-window mode. - final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo(); - final int logicalWidth = displayInfo.logicalWidth; - final int logicalHeight = displayInfo.logicalHeight; - - final int taskLeft = logicalWidth / 5; - final int taskTop = logicalHeight / 5; - final int taskRight = logicalWidth / 4 * 3; - final int taskBottom = logicalHeight / 4 * 3; - final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom); - TaskWithBounds task = new TaskWithBounds(taskBounds); - task.mInsetBounds.set(taskLeft, taskTop, taskRight, taskBottom); - task.mFullscreenForTest = false; - WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); - w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, - pf /* contentFrame */, pf /* visibleFrame */, pf /* decorFrame */, - pf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT, false); - // For non fullscreen tasks the containing frame is based off the - // task bounds not the parent frame. - assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); - assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom); - assertRect(w.mContentInsets, 0, 0, 0, 0); - - // Now simulate switch to fullscreen for letterboxed app. - final int xInset = logicalWidth / 10; - final int yInset = logicalWidth / 10; - final Rect cf = new Rect(xInset, yInset, logicalWidth - xInset, logicalHeight - yInset); - Configuration config = new Configuration(w.mAppToken.getOverrideConfiguration()); - config.windowConfiguration.setBounds(cf); - w.mAppToken.onOverrideConfigurationChanged(config); - pf.set(0, 0, logicalWidth, logicalHeight); - task.mFullscreenForTest = true; - - w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, - cf /* contentFrame */, cf /* visibleFrame */, pf /* decorFrame */, - cf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT, false); - assertEquals(cf, w.mFrame); - assertEquals(cf, w.getContentFrameLw()); - assertRect(w.mContentInsets, 0, 0, 0, 0); - } - - @Test - public void testDisplayCutout() { - // Regular fullscreen task and window - Task task = new TaskWithBounds(null); - WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final Rect pf = new Rect(0, 0, 1000, 2000); - // Create a display cutout of size 50x50, aligned top-center - final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); - - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false); - - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50); - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0); - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0); - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0); - } - - @Test - public void testDisplayCutout_tempInsetBounds() { - // Regular fullscreen task and window - TaskWithBounds task = new TaskWithBounds(new Rect(0, -500, 1000, 1500)); - task.mFullscreenForTest = false; - task.mInsetBounds.set(0, 0, 1000, 2000); - WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); - w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; - - final Rect pf = new Rect(0, -500, 1000, 1500); - // Create a display cutout of size 50x50, aligned top-center - final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); - - w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false); - - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50); - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0); - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0); - assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0); - } - - private WindowStateWithTask createWindow(Task task, int width, int height) { - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION); - attrs.width = width; - attrs.height = height; - - return new WindowStateWithTask(attrs, task); - } - -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java deleted file mode 100644 index d91079e3bb1b..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRule.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import android.app.ActivityManagerInternal; -import android.content.Context; -import android.hardware.display.DisplayManagerInternal; -import android.os.PowerManagerInternal; -import android.os.PowerSaveState; -import android.support.test.InstrumentationRegistry; -import android.view.InputChannel; - -import com.android.server.LocalServices; -import com.android.server.input.InputManagerService; -import com.android.server.policy.WindowManagerPolicy; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.invocation.InvocationOnMock; - -/** - * A test rule that sets up a fresh WindowManagerService instance before each test and makes sure - * to properly tear it down after. - * - *

- * Usage: - *

- * {@literal @}Rule
- *  public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
- * 
- */ -public class WindowManagerServiceRule implements TestRule { - - private WindowManagerService mService; - private TestWindowManagerPolicy mPolicy; - - @Override - public Statement apply(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - runWithDexmakerShareClassLoader(this::setUp); - try { - base.evaluate(); - } finally { - tearDown(); - } - } - - private void setUp() { - final Context context = InstrumentationRegistry.getTargetContext(); - - removeServices(); - - LocalServices.addService(DisplayManagerInternal.class, - mock(DisplayManagerInternal.class)); - - LocalServices.addService(PowerManagerInternal.class, - mock(PowerManagerInternal.class)); - final PowerManagerInternal pm = - LocalServices.getService(PowerManagerInternal.class); - PowerSaveState state = new PowerSaveState.Builder().build(); - doReturn(state).when(pm).getLowPowerState(anyInt()); - - LocalServices.addService(ActivityManagerInternal.class, - mock(ActivityManagerInternal.class)); - final ActivityManagerInternal am = - LocalServices.getService(ActivityManagerInternal.class); - doAnswer((InvocationOnMock invocationOnMock) -> { - final Runnable runnable = invocationOnMock.getArgument(0); - if (runnable != null) { - runnable.run(); - } - return null; - }).when(am).notifyKeyguardFlagsChanged(any()); - - InputManagerService ims = mock(InputManagerService.class); - // InputChannel is final and can't be mocked. - InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM); - if (input != null && input.length > 1) { - doReturn(input[1]).when(ims).monitorInput(anyString()); - } - - mService = WindowManagerService.main(context, ims, true, false, - false, mPolicy = new TestWindowManagerPolicy( - WindowManagerServiceRule.this::getWindowManagerService)); - - mService.onInitReady(); - - // Display creation is driven by the ActivityManagerService via ActivityStackSupervisor. - // We emulate those steps here. - mService.mRoot.createDisplayContent( - mService.mDisplayManager.getDisplay(DEFAULT_DISPLAY), - mock(DisplayWindowController.class)); - } - - private void removeServices() { - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.removeServiceForTest(PowerManagerInternal.class); - LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.removeServiceForTest(WindowManagerInternal.class); - LocalServices.removeServiceForTest(WindowManagerPolicy.class); - } - - private void tearDown() { - waitUntilWindowManagerHandlersIdle(); - removeServices(); - mService = null; - mPolicy = null; - } - }; - } - - public WindowManagerService getWindowManagerService() { - return mService; - } - - public TestWindowManagerPolicy getWindowManagerPolicy() { - return mPolicy; - } - - public void waitUntilWindowManagerHandlersIdle() { - final WindowManagerService wm = getWindowManagerService(); - if (wm != null) { - wm.mH.runWithScissors(() -> { }, 0); - wm.mAnimationHandler.runWithScissors(() -> { }, 0); - SurfaceAnimationThread.getHandler().runWithScissors(() -> { }, 0); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java deleted file mode 100644 index 6cf6d7bc96ec..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowManagerServiceRuleTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertThat; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@Presubmit -@SmallTest -public class WindowManagerServiceRuleTest { - - @Rule - public final WindowManagerServiceRule mRule = new WindowManagerServiceRule(); - - @Test - public void testWindowManagerSetUp() { - assertThat(mRule.getWindowManagerService(), notNullValue()); - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java deleted file mode 100644 index 85e846db60b7..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.view.WindowManager; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import java.util.LinkedList; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for the {@link WindowState} class. - * - * atest FrameworksServicesTests:com.android.server.wm.WindowStateTests - */ -@SmallTest -@FlakyTest(bugId = 74078662) -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowStateTests extends WindowTestsBase { - - @Test - public void testIsParentWindowHidden() throws Exception { - final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); - final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); - - // parentWindow is initially set to hidden. - assertTrue(parentWindow.mHidden); - assertFalse(parentWindow.isParentWindowHidden()); - assertTrue(child1.isParentWindowHidden()); - assertTrue(child2.isParentWindowHidden()); - - parentWindow.mHidden = false; - assertFalse(parentWindow.isParentWindowHidden()); - assertFalse(child1.isParentWindowHidden()); - assertFalse(child2.isParentWindowHidden()); - - } - - @Test - public void testIsChildWindow() throws Exception { - final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); - final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); - final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow"); - - assertFalse(parentWindow.isChildWindow()); - assertTrue(child1.isChildWindow()); - assertTrue(child2.isChildWindow()); - assertFalse(randomWindow.isChildWindow()); - } - - @Test - public void testHasChild() throws Exception { - final WindowState win1 = createWindow(null, TYPE_APPLICATION, "win1"); - final WindowState win11 = createWindow(win1, FIRST_SUB_WINDOW, "win11"); - final WindowState win12 = createWindow(win1, FIRST_SUB_WINDOW, "win12"); - final WindowState win2 = createWindow(null, TYPE_APPLICATION, "win2"); - final WindowState win21 = createWindow(win2, FIRST_SUB_WINDOW, "win21"); - final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow"); - - assertTrue(win1.hasChild(win11)); - assertTrue(win1.hasChild(win12)); - assertTrue(win2.hasChild(win21)); - - assertFalse(win1.hasChild(win21)); - assertFalse(win1.hasChild(randomWindow)); - - assertFalse(win2.hasChild(win11)); - assertFalse(win2.hasChild(win12)); - assertFalse(win2.hasChild(randomWindow)); - } - - @Test - public void testGetParentWindow() throws Exception { - final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); - final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); - - assertNull(parentWindow.getParentWindow()); - assertEquals(parentWindow, child1.getParentWindow()); - assertEquals(parentWindow, child2.getParentWindow()); - } - - @Test - public void testOverlayWindowHiddenWhenSuspended() { - final WindowState overlayWindow = spy(createWindow(null, TYPE_APPLICATION_OVERLAY, - "overlayWindow")); - overlayWindow.setHiddenWhileSuspended(true); - verify(overlayWindow).hideLw(true, true); - overlayWindow.setHiddenWhileSuspended(false); - verify(overlayWindow).showLw(true, true); - } - - @Test - public void testGetTopParentWindow() throws Exception { - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); - - assertEquals(root, root.getTopParentWindow()); - assertEquals(root, child1.getTopParentWindow()); - assertEquals(child1, child2.getParentWindow()); - assertEquals(root, child2.getTopParentWindow()); - - // Test case were child is detached from parent. - root.removeChild(child1); - assertEquals(child1, child1.getTopParentWindow()); - assertEquals(child1, child2.getParentWindow()); - } - - @Test - public void testIsOnScreen_hiddenByPolicy() { - final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); - window.setHasSurface(true); - assertTrue(window.isOnScreen()); - window.hideLw(false /* doAnimation */); - assertFalse(window.isOnScreen()); - } - - @Test - public void testCanBeImeTarget() throws Exception { - final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); - final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow"); - - // Setting FLAG_NOT_FOCUSABLE without FLAG_ALT_FOCUSABLE_IM prevents the window from being - // an IME target. - appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - - // Make windows visible - appWindow.setHasSurface(true); - imeWindow.setHasSurface(true); - - // Windows without flags (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM) can't be IME targets - assertFalse(appWindow.canBeImeTarget()); - assertFalse(imeWindow.canBeImeTarget()); - - // Add IME target flags - appWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM); - imeWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM); - - // Visible app window with flags can be IME target while an IME window can never be an IME - // target regardless of its visibility or flags. - assertTrue(appWindow.canBeImeTarget()); - assertFalse(imeWindow.canBeImeTarget()); - - // Make windows invisible - appWindow.hideLw(false /* doAnimation */); - imeWindow.hideLw(false /* doAnimation */); - - // Invisible window can't be IME targets even if they have the right flags. - assertFalse(appWindow.canBeImeTarget()); - assertFalse(imeWindow.canBeImeTarget()); - } - - @Test - public void testGetWindow() throws Exception { - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild"); - final WindowState mediaOverlayChild = createWindow(root, - TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild"); - final WindowState attachedDialogChild = createWindow(root, - TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild"); - final WindowState subPanelChild = createWindow(root, - TYPE_APPLICATION_SUB_PANEL, "subPanelChild"); - final WindowState aboveSubPanelChild = createWindow(root, - TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild"); - - final LinkedList windows = new LinkedList(); - - root.getWindow(w -> { - windows.addLast(w); - return false; - }); - - // getWindow should have returned candidate windows in z-order. - assertEquals(aboveSubPanelChild, windows.pollFirst()); - assertEquals(subPanelChild, windows.pollFirst()); - assertEquals(attachedDialogChild, windows.pollFirst()); - assertEquals(root, windows.pollFirst()); - assertEquals(mediaOverlayChild, windows.pollFirst()); - assertEquals(mediaChild, windows.pollFirst()); - assertTrue(windows.isEmpty()); - } - - @Test - public void testPrepareWindowToDisplayDuringRelayout() throws Exception { - testPrepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - testPrepareWindowToDisplayDuringRelayout(true /*wasVisible*/); - - // Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON - // before calling prepareWindowToDisplayDuringRelayout for windows with flag in the same - // appWindowToken. - final AppWindowToken appWindowToken = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final WindowState first = createWindow(null, TYPE_APPLICATION, appWindowToken, "first"); - final WindowState second = createWindow(null, TYPE_APPLICATION, appWindowToken, "second"); - second.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - - reset(mPowerManagerWrapper); - first.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(mPowerManagerWrapper, never()).wakeUp(anyLong(), anyString()); - assertTrue(appWindowToken.canTurnScreenOn()); - - reset(mPowerManagerWrapper); - second.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); - assertFalse(appWindowToken.canTurnScreenOn()); - - // Call prepareWindowToDisplayDuringRelayout for two window that have FLAG_TURN_SCREEN_ON - // from the same appWindowToken. Only one should trigger the wakeup. - appWindowToken.setCanTurnScreenOn(true); - first.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - second.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - - reset(mPowerManagerWrapper); - first.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); - assertFalse(appWindowToken.canTurnScreenOn()); - - reset(mPowerManagerWrapper); - second.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(mPowerManagerWrapper, never()).wakeUp(anyLong(), anyString()); - assertFalse(appWindowToken.canTurnScreenOn()); - - // Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an - // appWindowToken. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup - final WindowToken windowToken = WindowTestUtils.createTestWindowToken(FIRST_SUB_WINDOW, - mDisplayContent); - final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken, - "firstWindow"); - final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken, - "secondWindow"); - firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - - reset(mPowerManagerWrapper); - firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); - - reset(mPowerManagerWrapper); - secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); - verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); - } - - @Test - public void testCanAffectSystemUiFlags() throws Exception { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - app.mToken.setHidden(false); - assertTrue(app.canAffectSystemUiFlags()); - app.mToken.setHidden(true); - assertFalse(app.canAffectSystemUiFlags()); - app.mToken.setHidden(false); - app.mAttrs.alpha = 0.0f; - assertFalse(app.canAffectSystemUiFlags()); - - } - - @Test - public void testCanAffectSystemUiFlags_disallow() throws Exception { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - app.mToken.setHidden(false); - assertTrue(app.canAffectSystemUiFlags()); - app.getTask().setCanAffectSystemUiFlags(false); - assertFalse(app.canAffectSystemUiFlags()); - } - - @Test - public void testIsSelfOrAncestorWindowAnimating() throws Exception { - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); - assertFalse(child2.isSelfOrAncestorWindowAnimatingExit()); - child2.mAnimatingExit = true; - assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); - child2.mAnimatingExit = false; - root.mAnimatingExit = true; - assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); - } - - @Test - public void testLayoutSeqResetOnReparent() throws Exception { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - app.mLayoutSeq = 1; - mDisplayContent.mLayoutSeq = 1; - - app.onDisplayChanged(mDisplayContent); - - assertThat(app.mLayoutSeq, not(is(mDisplayContent.mLayoutSeq))); - } - - @Test - public void testDisplayIdUpdatedOnReparent() throws Exception { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - // fake a different display - app.mInputWindowHandle.displayId = mDisplayContent.getDisplayId() + 1; - app.onDisplayChanged(mDisplayContent); - - assertThat(app.mInputWindowHandle.displayId, is(mDisplayContent.getDisplayId())); - assertThat(app.getDisplayId(), is(mDisplayContent.getDisplayId())); - } - - private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) { - reset(mPowerManagerWrapper); - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; - - root.prepareWindowToDisplayDuringRelayout(wasVisible /*wasVisible*/); - verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java deleted file mode 100644 index e173b7db2da7..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowSurfacePlacerTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.view.WindowManager.TRANSIT_TASK_CLOSE; -import static android.view.WindowManager.TRANSIT_TASK_OPEN; -import static junit.framework.Assert.assertEquals; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.WindowManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowSurfacePlacerTest extends WindowTestsBase { - - private WindowSurfacePlacer mWindowSurfacePlacer; - - @Before - public void setUp() throws Exception { - super.setUp(); - mWindowSurfacePlacer = new WindowSurfacePlacer(sWm); - } - - @Test - public void testTranslucentOpen() throws Exception { - synchronized (sWm.mWindowMap) { - final AppWindowToken behind = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentOpening.setFillsParent(false); - translucentOpening.setHidden(true); - sWm.mOpeningApps.add(behind); - sWm.mOpeningApps.add(translucentOpening); - assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN, - mWindowSurfacePlacer.maybeUpdateTransitToTranslucentAnim(TRANSIT_TASK_OPEN)); - } - } - - @Test - public void testTranslucentClose() throws Exception { - synchronized (sWm.mWindowMap) { - final AppWindowToken behind = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final AppWindowToken translucentClosing = createAppWindowToken(mDisplayContent, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - translucentClosing.setFillsParent(false); - sWm.mClosingApps.add(translucentClosing); - assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE, - mWindowSurfacePlacer.maybeUpdateTransitToTranslucentAnim(TRANSIT_TASK_CLOSE)); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java deleted file mode 100644 index 2e4740b57255..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.Binder; -import android.os.IBinder; -import android.view.IApplicationToken; -import android.view.IWindow; -import android.view.WindowManager; - -import static android.app.AppOpsManager.OP_NONE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - -import static com.android.server.wm.WindowContainer.POSITION_TOP; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyFloat; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.mockito.invocation.InvocationOnMock; - -/** - * A collection of static functions that can be referenced by other test packages to provide access - * to WindowManager related test functionality. - */ -public class WindowTestUtils { - public static int sNextTaskId = 0; - - /** - * Retrieves an instance of a mock {@link WindowManagerService}. - */ - public static WindowManagerService getMockWindowManagerService() { - final WindowManagerService service = mock(WindowManagerService.class); - final WindowHashMap windowMap = new WindowHashMap(); - when(service.getWindowManagerLock()).thenReturn(windowMap); - return service; - } - - /** - * Creates a mock instance of {@link StackWindowController}. - */ - public static StackWindowController createMockStackWindowContainerController() { - StackWindowController controller = mock(StackWindowController.class); - controller.mContainer = mock(TestTaskStack.class); - - // many components rely on the {@link StackWindowController#adjustConfigurationForBounds} - // to properly set bounds values in the configuration. We must mimick those actions here. - doAnswer((InvocationOnMock invocationOnMock) -> { - final Configuration config = invocationOnMock.getArgument(7); - final Rect bounds = invocationOnMock.getArgument(0); - config.windowConfiguration.setBounds(bounds); - return null; - }).when(controller).adjustConfigurationForBounds(any(), any(), any(), any(), - anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt()); - - return controller; - } - - /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */ - public static Task createTaskInStack(WindowManagerService service, TaskStack stack, - int userId) { - synchronized (service.mWindowMap) { - final Task newTask = new Task(sNextTaskId++, stack, userId, service, 0, false, - new ActivityManager.TaskDescription(), null); - stack.addTask(newTask, POSITION_TOP); - return newTask; - } - } - - /** - * An extension of {@link TestTaskStack}, which overrides package scoped methods that would not - * normally be mocked out. - */ - public static class TestTaskStack extends TaskStack { - TestTaskStack(WindowManagerService service, int stackId) { - super(service, stackId, null); - } - - @Override - void addTask(Task task, int position, boolean showForAllUsers, boolean moveParents) { - // Do nothing. - } - } - - static TestAppWindowToken createTestAppWindowToken(DisplayContent dc) { - synchronized (dc.mService.mWindowMap) { - return new TestAppWindowToken(dc); - } - } - - /** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */ - public static class TestAppWindowToken extends AppWindowToken { - boolean mOnTop = false; - - private TestAppWindowToken(DisplayContent dc) { - super(dc.mService, new IApplicationToken.Stub() { - public String getName() {return null;} - }, false, dc, true /* fillsParent */); - } - - TestAppWindowToken(WindowManagerService service, IApplicationToken token, - boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, - boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, - int rotationAnimationHint, int configChanges, boolean launchTaskBehind, - boolean alwaysFocusable, AppWindowContainerController controller) { - super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen, - showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges, - launchTaskBehind, alwaysFocusable, controller); - } - - int getWindowsCount() { - return mChildren.size(); - } - - boolean hasWindow(WindowState w) { - return mChildren.contains(w); - } - - WindowState getFirstChild() { - return mChildren.peekFirst(); - } - - WindowState getLastChild() { - return mChildren.peekLast(); - } - - int positionInParent() { - return getParent().mChildren.indexOf(this); - } - - void setIsOnTop(boolean onTop) { - mOnTop = onTop; - } - - @Override - boolean isOnTop() { - return mOnTop; - } - } - - static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { - return createTestWindowToken(type, dc, false /* persistOnEmpty */); - } - - static TestWindowToken createTestWindowToken(int type, DisplayContent dc, - boolean persistOnEmpty) { - synchronized (dc.mService.mWindowMap) { - return new TestWindowToken(type, dc, persistOnEmpty); - } - } - - /* Used so we can gain access to some protected members of the {@link WindowToken} class */ - public static class TestWindowToken extends WindowToken { - - private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) { - super(dc.mService, mock(IBinder.class), type, persistOnEmpty, dc, - false /* ownerCanManageAppTokens */); - } - - int getWindowsCount() { - return mChildren.size(); - } - - boolean hasWindow(WindowState w) { - return mChildren.contains(w); - } - } - - /* Used so we can gain access to some protected members of the {@link Task} class */ - public static class TestTask extends Task { - boolean mShouldDeferRemoval = false; - boolean mOnDisplayChangedCalled = false; - private boolean mIsAnimating = false; - - TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, - int resizeMode, boolean supportsPictureInPicture, - TaskWindowContainerController controller) { - super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture, - new ActivityManager.TaskDescription(), controller); - } - - boolean shouldDeferRemoval() { - return mShouldDeferRemoval; - } - - int positionInParent() { - return getParent().mChildren.indexOf(this); - } - - @Override - void onDisplayChanged(DisplayContent dc) { - super.onDisplayChanged(dc); - mOnDisplayChangedCalled = true; - } - - @Override - boolean isSelfAnimating() { - return mIsAnimating; - } - - void setLocalIsAnimating(boolean isAnimating) { - mIsAnimating = isAnimating; - } - } - - /** - * Used so we can gain access to some protected members of {@link TaskWindowContainerController} - * class. - */ - public static class TestTaskWindowContainerController extends TaskWindowContainerController { - - TestTaskWindowContainerController(WindowTestsBase testsBase) { - this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent)); - } - - TestTaskWindowContainerController(StackWindowController stackController) { - super(sNextTaskId++, new TaskWindowContainerListener() { - @Override - public void registerConfigurationChangeListener( - ConfigurationContainerListener listener) { - - } - - @Override - public void unregisterConfigurationChangeListener( - ConfigurationContainerListener listener) { - - } - - @Override - public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) { - - } - - @Override - public void requestResize(Rect bounds, int resizeMode) { - - } - }, stackController, 0 /* userId */, null /* bounds */, RESIZE_MODE_UNRESIZEABLE, - false /* supportsPictureInPicture */, true /* toTop*/, - true /* showForAllUsers */, new ActivityManager.TaskDescription(), - stackController.mService); - } - - @Override - TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode, - boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) { - return new TestTask(taskId, stack, userId, mService, resizeMode, - supportsPictureInPicture, this); - } - } - - public static class TestAppWindowContainerController extends AppWindowContainerController { - - final IApplicationToken mToken; - - TestAppWindowContainerController(TestTaskWindowContainerController taskController) { - this(taskController, new TestIApplicationToken()); - } - - TestAppWindowContainerController(TestTaskWindowContainerController taskController, - IApplicationToken token) { - super(taskController, token, null /* listener */, 0 /* index */, - SCREEN_ORIENTATION_UNSPECIFIED, true /* fullscreen */, - true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */, - false /* launchTaskBehind */, false /* alwaysFocusable */, - 0 /* targetSdkVersion */, 0 /* rotationAnimationHint */, - 0 /* inputDispatchingTimeoutNanos */, taskController.mService); - mToken = token; - } - - @Override - AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token, - boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, - boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, - int rotationAnimationHint, int configChanges, boolean launchTaskBehind, - boolean alwaysFocusable, AppWindowContainerController controller) { - return new TestAppWindowToken(service, token, voiceInteraction, dc, - inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, - orientation, - rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, - controller); - } - - AppWindowToken getAppWindowToken(DisplayContent dc) { - return (AppWindowToken) dc.getWindowToken(mToken.asBinder()); - } - } - - public static class TestIApplicationToken implements IApplicationToken { - - private final Binder mBinder = new Binder(); - @Override - public IBinder asBinder() { - return mBinder; - } - @Override - public String getName() { - return null; - } - } - - /** Used to track resize reports. */ - public static class TestWindowState extends WindowState { - boolean resizeReported; - - TestWindowState(WindowManagerService service, Session session, IWindow window, - WindowManager.LayoutParams attrs, WindowToken token) { - super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, - false /* ownerCanAddInternalSystemWindow */); - } - - @Override - void reportResized() { - super.reportResized(); - resizeReported = true; - } - - @Override - public boolean isGoneForLayoutLw() { - return false; - } - - @Override - void updateResizingWindowIfNeeded() { - // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive - // the system that it can actually update the window. - boolean hadSurface = mHasSurface; - mHasSurface = true; - - super.updateResizingWindowIfNeeded(); - - mHasSurface = hadSurface; - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java deleted file mode 100644 index 473a287e3d9c..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.View.VISIBLE; - -import android.content.res.Configuration; -import android.graphics.Rect; -import android.hardware.display.DisplayManagerGlobal; -import android.testing.DexmakerShareClassLoaderRule; -import android.util.Log; -import android.view.Display; -import android.view.DisplayInfo; -import org.junit.Assert; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.view.IWindow; -import android.view.WindowManager; - -import static android.app.AppOpsManager.OP_NONE; -import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; -import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static org.mockito.Mockito.mock; - -import com.android.server.AttributeCache; - -import java.util.HashSet; -import java.util.LinkedList; - -/** - * Common base class for window manager unit test classes. - * - * Make sure any requests to WM hold the WM lock if needed b/73966377 - */ -class WindowTestsBase { - private static final String TAG = WindowTestsBase.class.getSimpleName(); - WindowManagerService sWm = null; // TODO(roosa): rename to mWm in follow-up CL - private final IWindow mIWindow = new TestIWindow(); - private Session mMockSession; - // The default display is removed in {@link #setUp} and then we iterate over all displays to - // make sure we don't collide with any existing display. If we run into no other display, the - // added display should be treated as default. This cannot be the default display - private static int sNextDisplayId = DEFAULT_DISPLAY + 1; - static int sNextStackId = 1000; - - DisplayContent mDisplayContent; - DisplayInfo mDisplayInfo = new DisplayInfo(); - WindowState mWallpaperWindow; - WindowState mImeWindow; - WindowState mImeDialogWindow; - WindowState mStatusBarWindow; - WindowState mDockedDividerWindow; - WindowState mNavBarWindow; - WindowState mAppWindow; - WindowState mChildAppWindowAbove; - WindowState mChildAppWindowBelow; - HashSet mCommonWindows; - WallpaperController mWallpaperController; - - @Rule - public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = - new DexmakerShareClassLoaderRule(); - - @Rule - public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule(); - - static WindowState.PowerManagerWrapper mPowerManagerWrapper; // TODO(roosa): make non-static. - - @Before - public void setUp() throws Exception { - // If @Before throws an exception, the error isn't logged. This will make sure any failures - // in the set up are clear. This can be removed when b/37850063 is fixed. - try { - mMockSession = mock(Session.class); - mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class); - - final Context context = InstrumentationRegistry.getTargetContext(); - AttributeCache.init(context); - - sWm = mWmRule.getWindowManagerService(); - beforeCreateDisplay(); - - mWallpaperController = new WallpaperController(sWm); - - context.getDisplay().getDisplayInfo(mDisplayInfo); - mDisplayContent = createNewDisplay(); - sWm.mDisplayEnabled = true; - sWm.mDisplayReady = true; - - // Set-up some common windows. - mCommonWindows = new HashSet(); - synchronized (sWm.mWindowMap) { - mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow"); - mImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "mImeWindow"); - sWm.mInputMethodWindow = mImeWindow; - mImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG, - "mImeDialogWindow"); - mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow"); - mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow"); - mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER, - "mDockedDividerWindow"); - mAppWindow = createCommonWindow(null, TYPE_BASE_APPLICATION, "mAppWindow"); - mChildAppWindowAbove = createCommonWindow(mAppWindow, - TYPE_APPLICATION_ATTACHED_DIALOG, - "mChildAppWindowAbove"); - mChildAppWindowBelow = createCommonWindow(mAppWindow, - TYPE_APPLICATION_MEDIA_OVERLAY, - "mChildAppWindowBelow"); - } - // Adding a display will cause freezing the display. Make sure to wait until it's - // unfrozen to not run into race conditions with the tests. - waitUntilHandlersIdle(); - } catch (Exception e) { - Log.e(TAG, "Failed to set up test", e); - throw e; - } - } - - void beforeCreateDisplay() { - // Called before display is created. - } - - @After - public void tearDown() throws Exception { - // If @After throws an exception, the error isn't logged. This will make sure any failures - // in the tear down are clear. This can be removed when b/37850063 is fixed. - try { - final LinkedList nonCommonWindows = new LinkedList(); - - synchronized (sWm.mWindowMap) { - sWm.mRoot.forAllWindows(w -> { - if (!mCommonWindows.contains(w)) { - nonCommonWindows.addLast(w); - } - }, true /* traverseTopToBottom */); - - while (!nonCommonWindows.isEmpty()) { - nonCommonWindows.pollLast().removeImmediately(); - } - - mDisplayContent.removeImmediately(); - sWm.mInputMethodTarget = null; - sWm.mClosingApps.clear(); - sWm.mOpeningApps.clear(); - } - - // Wait until everything is really cleaned up. - waitUntilHandlersIdle(); - } catch (Exception e) { - Log.e(TAG, "Failed to tear down test", e); - throw e; - } - } - - /** - * @return A SurfaceBuilderFactory to inject in to the WindowManagerService during - * set-up (or null). - */ - SurfaceBuilderFactory getSurfaceBuilderFactory() { - return null; - } - - private WindowState createCommonWindow(WindowState parent, int type, String name) { - synchronized (sWm.mWindowMap) { - final WindowState win = createWindow(parent, type, name); - mCommonWindows.add(win); - // Prevent common windows from been IMe targets - win.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - return win; - } - } - - /** Asserts that the first entry is greater than the second entry. */ - void assertGreaterThan(int first, int second) throws Exception { - Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second); - } - - /** Asserts that the first entry is greater than the second entry. */ - void assertLessThan(int first, int second) throws Exception { - Assert.assertTrue("Excepted " + first + " to be less than " + second, first < second); - } - - /** - * Waits until the main handler for WM has processed all messages. - */ - void waitUntilHandlersIdle() { - mWmRule.waitUntilWindowManagerHandlersIdle(); - } - - private WindowToken createWindowToken( - DisplayContent dc, int windowingMode, int activityType, int type) { - synchronized (sWm.mWindowMap) { - if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) { - return WindowTestUtils.createTestWindowToken(type, dc); - } - - return createAppWindowToken(dc, windowingMode, activityType); - } - } - - AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) { - final TaskStack stack = createStackControllerOnStackOnDisplay(windowingMode, activityType, - dc).mContainer; - final Task task = createTaskInStack(stack, 0 /* userId */); - final WindowTestUtils.TestAppWindowToken appWindowToken = - WindowTestUtils.createTestAppWindowToken(dc); - task.addChild(appWindowToken, 0); - return appWindowToken; - } - - WindowState createWindow(WindowState parent, int type, String name) { - synchronized (sWm.mWindowMap) { - return (parent == null) - ? createWindow(parent, type, mDisplayContent, name) - : createWindow(parent, type, parent.mToken, name); - } - } - - WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType, - int type, DisplayContent dc, String name) { - synchronized (sWm.mWindowMap) { - final WindowToken token = createWindowToken(dc, windowingMode, activityType, type); - return createWindow(parent, type, token, name); - } - } - - WindowState createAppWindow(Task task, int type, String name) { - synchronized (sWm.mWindowMap) { - final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); - task.addChild(token, 0); - return createWindow(null, type, token, name); - } - } - - WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) { - synchronized (sWm.mWindowMap) { - final WindowToken token = createWindowToken( - dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); - return createWindow(parent, type, token, name); - } - } - - WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name, - boolean ownerCanAddInternalSystemWindow) { - synchronized (sWm.mWindowMap) { - final WindowToken token = createWindowToken( - dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); - return createWindow(parent, type, token, name, 0 /* ownerId */, - ownerCanAddInternalSystemWindow); - } - } - - WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { - synchronized (sWm.mWindowMap) { - return createWindow(parent, type, token, name, 0 /* ownerId */, - false /* ownerCanAddInternalSystemWindow */); - } - } - - WindowState createWindow(WindowState parent, int type, WindowToken token, String name, - int ownerId, boolean ownerCanAddInternalSystemWindow) { - return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow, - sWm, mMockSession, mIWindow); - } - - static WindowState createWindow(WindowState parent, int type, WindowToken token, - String name, int ownerId, boolean ownerCanAddInternalSystemWindow, - WindowManagerService service, Session session, IWindow iWindow) { - synchronized (service.mWindowMap) { - final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); - attrs.setTitle(name); - - final WindowState w = new WindowState(service, session, iWindow, token, parent, - OP_NONE, - 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow, - mPowerManagerWrapper); - // TODO: Probably better to make this call in the WindowState ctor to avoid errors with - // adding it to the token... - token.addWindow(w); - return w; - } - } - - /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */ - TaskStack createTaskStackOnDisplay(DisplayContent dc) { - synchronized (sWm.mWindowMap) { - return createStackControllerOnDisplay(dc).mContainer; - } - } - - StackWindowController createStackControllerOnDisplay(DisplayContent dc) { - synchronized (sWm.mWindowMap) { - return createStackControllerOnStackOnDisplay( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc); - } - } - - StackWindowController createStackControllerOnStackOnDisplay( - int windowingMode, int activityType, DisplayContent dc) { - synchronized (sWm.mWindowMap) { - final Configuration overrideConfig = new Configuration(); - overrideConfig.windowConfiguration.setWindowingMode(windowingMode); - overrideConfig.windowConfiguration.setActivityType(activityType); - final int stackId = ++sNextStackId; - final StackWindowController controller = new StackWindowController(stackId, null, - dc.getDisplayId(), true /* onTop */, new Rect(), sWm); - controller.onOverrideConfigurationChanged(overrideConfig); - return controller; - } - } - - /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */ - Task createTaskInStack(TaskStack stack, int userId) { - return WindowTestUtils.createTaskInStack(sWm, stack, userId); - } - - /** Creates a {@link DisplayContent} and adds it to the system. */ - DisplayContent createNewDisplay() { - final int displayId = sNextDisplayId++; - final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, - mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - synchronized (sWm.mWindowMap) { - return new DisplayContent(display, sWm, mWallpaperController, - mock(DisplayWindowController.class)); - } - } - - /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */ - WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs, - WindowToken token) { - synchronized (sWm.mWindowMap) { - return new WindowTestUtils.TestWindowState(sWm, mMockSession, mIWindow, attrs, token); - } - } - -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java deleted file mode 100644 index e3b717435052..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -/** - * Tests for the {@link WindowToken} class. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.WindowTokenTests - */ -@SmallTest -@FlakyTest(bugId = 74078662) -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowTokenTests extends WindowTestsBase { - - @Test - public void testAddWindow() throws Exception { - final WindowTestUtils.TestWindowToken token = - WindowTestUtils.createTestWindowToken(0, mDisplayContent); - - assertEquals(0, token.getWindowsCount()); - - final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1"); - final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11"); - final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12"); - final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2"); - final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3"); - - token.addWindow(window1); - // NOTE: Child windows will not be added to the token as window containers can only - // contain/reference their direct children. - token.addWindow(window11); - token.addWindow(window12); - token.addWindow(window2); - token.addWindow(window3); - - // Should not contain the child windows that were added above. - assertEquals(3, token.getWindowsCount()); - assertTrue(token.hasWindow(window1)); - assertFalse(token.hasWindow(window11)); - assertFalse(token.hasWindow(window12)); - assertTrue(token.hasWindow(window2)); - assertTrue(token.hasWindow(window3)); - } - - @Test - public void testChildRemoval() throws Exception { - final DisplayContent dc = mDisplayContent; - final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(0, dc); - - assertEquals(token, dc.getWindowToken(token.token)); - - final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1"); - final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2"); - - window2.removeImmediately(); - // The token should still be mapped in the display content since it still has a child. - assertEquals(token, dc.getWindowToken(token.token)); - - window1.removeImmediately(); - // The token should have been removed from the display content since it no longer has a - // child. - assertEquals(null, dc.getWindowToken(token.token)); - } - - /** - * Test that a window token isn't orphaned by the system when it is requested to be removed. - * Tokens should only be removed from the system when all their windows are gone. - */ - @Test - public void testTokenRemovalProcess() throws Exception { - final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken( - TYPE_TOAST, mDisplayContent, true /* persistOnEmpty */); - - // Verify that the token is on the display - assertNotNull(mDisplayContent.getWindowToken(token.token)); - - final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1"); - final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2"); - - mDisplayContent.removeWindowToken(token.token); - // Verify that the token is no longer mapped on the display - assertNull(mDisplayContent.getWindowToken(token.token)); - // Verify that the token is still attached to its parent - assertNotNull(token.getParent()); - // Verify that the token windows are still around. - assertEquals(2, token.getWindowsCount()); - - window1.removeImmediately(); - // Verify that the token is still attached to its parent - assertNotNull(token.getParent()); - // Verify that the other token window is still around. - assertEquals(1, token.getWindowsCount()); - - window2.removeImmediately(); - // Verify that the token is no-longer attached to its parent - assertNull(token.getParent()); - // Verify that the token windows are no longer attached to it. - assertEquals(0, token.getWindowsCount()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java deleted file mode 100644 index 50852548f525..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTracingTest.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; -import android.support.test.InstrumentationRegistry; -import android.support.test.filters.SmallTest; -import android.support.test.filters.FlakyTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.util.Preconditions; -import com.android.server.wm.WindowManagerTraceProto; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; - -/** - * Test class for {@link WindowTracing}. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.WindowTracingTest - */ -@SmallTest -@FlakyTest(bugId = 74078662) -@Presubmit -@RunWith(AndroidJUnit4.class) -public class WindowTracingTest extends WindowTestsBase { - - private static final byte[] MAGIC_HEADER = new byte[] { - 0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45, - }; - - private Context mTestContext; - private WindowTracing mWindowTracing; - private WindowManagerService mWmMock; - private File mFile; - - @Override - @Before - public void setUp() throws Exception { - super.setUp(); - - mWmMock = mock(WindowManagerService.class); - - mTestContext = InstrumentationRegistry.getContext(); - - mFile = mTestContext.getFileStreamPath("tracing_test.dat"); - mFile.delete(); - - mWindowTracing = new WindowTracing(mFile); - } - - @Test - public void isEnabled_returnsFalseByDefault() throws Exception { - assertFalse(mWindowTracing.isEnabled()); - } - - @Test - public void isEnabled_returnsTrueAfterStart() throws Exception { - mWindowTracing.startTrace(mock(PrintWriter.class)); - assertTrue(mWindowTracing.isEnabled()); - } - - @Test - public void isEnabled_returnsFalseAfterStop() throws Exception { - mWindowTracing.startTrace(mock(PrintWriter.class)); - mWindowTracing.stopTrace(mock(PrintWriter.class)); - assertFalse(mWindowTracing.isEnabled()); - } - - @Test - public void trace_discared_whenNotTracing() throws Exception { - mWindowTracing.traceStateLocked("where", mWmMock); - verifyZeroInteractions(mWmMock); - } - - @Test - public void trace_dumpsWindowManagerState_whenTracing() throws Exception { - mWindowTracing.startTrace(mock(PrintWriter.class)); - mWindowTracing.traceStateLocked("where", mWmMock); - - verify(mWmMock).writeToProtoLocked(any(), eq(true)); - } - - @Test - public void traceFile_startsWithMagicHeader() throws Exception { - mWindowTracing.startTrace(mock(PrintWriter.class)); - mWindowTracing.stopTrace(mock(PrintWriter.class)); - - byte[] header = new byte[MAGIC_HEADER.length]; - try (InputStream is = new FileInputStream(mFile)) { - assertEquals(MAGIC_HEADER.length, is.read(header)); - assertArrayEquals(MAGIC_HEADER, header); - } - } - - @Test - @Ignore("Figure out why this test is crashing when setting up mWmMock.") - public void tracing_endsUpInFile() throws Exception { - mWindowTracing.startTrace(mock(PrintWriter.class)); - - doAnswer((inv) -> { - inv.getArgument(0).write( - WindowManagerTraceProto.WHERE, "TEST_WM_PROTO"); - return null; - }).when(mWmMock).writeToProtoLocked(any(), any()); - mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock); - - mWindowTracing.stopTrace(mock(PrintWriter.class)); - - byte[] file = new byte[1000]; - int fileLength; - try (InputStream is = new FileInputStream(mFile)) { - fileLength = is.read(file); - assertTrue(containsBytes(file, fileLength, - "TEST_WHERE".getBytes(StandardCharsets.UTF_8))); - assertTrue(containsBytes(file, fileLength, - "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8))); - } - } - - @Override - @After - public void tearDown() throws Exception { - super.tearDown(); - - mFile.delete(); - } - - /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */ - boolean containsBytes(byte[] haystack, int haystackLenght, byte[] needle) { - Preconditions.checkArgument(haystackLenght > 0); - Preconditions.checkArgument(needle.length > 0); - - outer: for (int i = 0; i <= haystackLenght - needle.length; i++) { - for (int j = 0; j < needle.length; j++) { - if (haystack[i+j] != needle[j]) { - continue outer; - } - } - return true; - } - return false; - } - - @Test - public void test_containsBytes() { - byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8); - assertTrue(containsBytes(haystack, haystack.length, - "hello".getBytes(StandardCharsets.UTF_8))); - assertTrue(containsBytes(haystack, haystack.length, - "world".getBytes(StandardCharsets.UTF_8))); - assertFalse(containsBytes(haystack, 6, - "world".getBytes(StandardCharsets.UTF_8))); - assertFalse(containsBytes(haystack, haystack.length, - "world_".getBytes(StandardCharsets.UTF_8))); - assertFalse(containsBytes(haystack, haystack.length, - "absent".getBytes(StandardCharsets.UTF_8))); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java deleted file mode 100644 index 547be551c842..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; -import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.filters.FlakyTest; -import android.support.test.runner.AndroidJUnit4; -import android.view.SurfaceControl; -import android.view.SurfaceSession; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashMap; -import java.util.LinkedList; - -/** - * Tests for the {@link WindowLayersController} class. - * - * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.ZOrderingTests - */ -@SmallTest -@FlakyTest(bugId = 74078662) -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ZOrderingTests extends WindowTestsBase { - - private class LayerRecordingTransaction extends SurfaceControl.Transaction { - HashMap mLayersForControl = new HashMap(); - HashMap mRelativeLayersForControl = new HashMap(); - - @Override - public SurfaceControl.Transaction setLayer(SurfaceControl sc, int layer) { - mRelativeLayersForControl.remove(sc); - mLayersForControl.put(sc, layer); - return super.setLayer(sc, layer); - } - - @Override - public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc, - SurfaceControl relativeTo, - int layer) { - mRelativeLayersForControl.put(sc, relativeTo); - mLayersForControl.put(sc, layer); - return super.setRelativeLayer(sc, relativeTo, layer); - } - - private int getLayer(SurfaceControl sc) { - return mLayersForControl.getOrDefault(sc, 0); - } - - private SurfaceControl getRelativeLayer(SurfaceControl sc) { - return mRelativeLayersForControl.get(sc); - } - }; - - // We have WM use our Hierarchy recording subclass of SurfaceControl.Builder - // such that we can keep track of the parents of Surfaces as they are constructed. - private HashMap mParentFor = new HashMap(); - - private class HierarchyRecorder extends SurfaceControl.Builder { - SurfaceControl mPendingParent; - - HierarchyRecorder(SurfaceSession s) { - super(s); - } - - public SurfaceControl.Builder setParent(SurfaceControl sc) { - mPendingParent = sc; - return super.setParent(sc); - } - public SurfaceControl build() { - SurfaceControl sc = super.build(); - mParentFor.put(sc, mPendingParent); - mPendingParent = null; - return sc; - } - }; - - class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory { - public SurfaceControl.Builder make(SurfaceSession s) { - return new HierarchyRecorder(s); - } - }; - - private LayerRecordingTransaction mTransaction; - - @Override - void beforeCreateDisplay() { - // We can't use @Before here because it may happen after WindowTestsBase @Before - // which is after construction of the DisplayContent, meaning the HierarchyRecorder - // would miss construction of the top-level layers. - mTransaction = new LayerRecordingTransaction(); - sWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory(); - sWm.mTransactionFactory = () -> mTransaction; - } - - @After - public void after() { - mTransaction.close(); - mParentFor.clear(); - } - - LinkedList getAncestors(LayerRecordingTransaction t, SurfaceControl sc) { - LinkedList p = new LinkedList(); - SurfaceControl current = sc; - do { - p.addLast(current); - - SurfaceControl rs = t.getRelativeLayer(current); - if (rs != null) { - current = rs; - } else { - current = mParentFor.get(current); - } - } while (current != null); - return p; - } - - - void assertZOrderGreaterThan(LayerRecordingTransaction t, SurfaceControl left, - SurfaceControl right) throws Exception { - final LinkedList leftParentChain = getAncestors(t, left); - final LinkedList rightParentChain = getAncestors(t, right); - - SurfaceControl commonAncestor = null; - SurfaceControl leftTop = leftParentChain.peekLast(); - SurfaceControl rightTop = rightParentChain.peekLast(); - while (leftTop != null && rightTop != null && leftTop == rightTop) { - commonAncestor = leftParentChain.removeLast(); - rightParentChain.removeLast(); - leftTop = leftParentChain.peekLast(); - rightTop = rightParentChain.peekLast(); - } - - if (rightTop == null) { // right is the parent of left. - assertGreaterThan(t.getLayer(leftTop), 0); - } else if (leftTop == null) { // left is the parent of right. - assertGreaterThan(0, t.getLayer(rightTop)); - } else { - assertGreaterThan(t.getLayer(leftTop), - t.getLayer(rightTop)); - } - } - - void assertWindowHigher(WindowState left, WindowState right) throws Exception { - assertZOrderGreaterThan(mTransaction, left.getSurfaceControl(), right.getSurfaceControl()); - } - - WindowState createWindow(String name) { - return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name); - } - - @Test - public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception { - sWm.mInputMethodTarget = null; - mDisplayContent.assignChildLayers(mTransaction); - - // The Ime has an higher base layer than app windows and lower base layer than system - // windows, so it should be above app windows and below system windows if there isn't an IME - // target. - assertWindowHigher(mImeWindow, mChildAppWindowAbove); - assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mNavBarWindow, mImeWindow); - assertWindowHigher(mStatusBarWindow, mImeWindow); - - // And, IME dialogs should always have an higher layer than the IME. - assertWindowHigher(mImeDialogWindow, mImeWindow); - } - - @Test - public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception { - final WindowState imeAppTarget = createWindow("imeAppTarget"); - sWm.mInputMethodTarget = imeAppTarget; - - mDisplayContent.assignChildLayers(mTransaction); - - // Ime should be above all app windows and below system windows if it is targeting an app - // window. - assertWindowHigher(mImeWindow, imeAppTarget); - assertWindowHigher(mImeWindow, mChildAppWindowAbove); - assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mNavBarWindow, mImeWindow); - assertWindowHigher(mStatusBarWindow, mImeWindow); - - // And, IME dialogs should always have an higher layer than the IME. - assertWindowHigher(mImeDialogWindow, mImeWindow); - } - - @Test - public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception { - final WindowState imeAppTarget = createWindow("imeAppTarget"); - final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget, - TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken, - "imeAppTargetChildAboveWindow"); - final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget, - TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken, - "imeAppTargetChildBelowWindow"); - - sWm.mInputMethodTarget = imeAppTarget; - mDisplayContent.assignChildLayers(mTransaction); - - // Ime should be above all app windows except for child windows that are z-ordered above it - // and below system windows if it is targeting an app window. - assertWindowHigher(mImeWindow, imeAppTarget); - assertWindowHigher(imeAppTargetChildAboveWindow, mImeWindow); - assertWindowHigher(mImeWindow, mChildAppWindowAbove); - assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mNavBarWindow, mImeWindow); - assertWindowHigher(mStatusBarWindow, mImeWindow); - - // And, IME dialogs should always have an higher layer than the IME. - assertWindowHigher(mImeDialogWindow, mImeWindow); - } - - @Test - public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception { - final WindowState appBelowImeTarget = createWindow("appBelowImeTarget"); - final WindowState imeAppTarget = createWindow("imeAppTarget"); - final WindowState appAboveImeTarget = createWindow("appAboveImeTarget"); - - sWm.mInputMethodTarget = imeAppTarget; - mDisplayContent.assignChildLayers(mTransaction); - - // Ime should be above all app windows except for non-fullscreen app window above it and - // below system windows if it is targeting an app window. - assertWindowHigher(mImeWindow, imeAppTarget); - assertWindowHigher(mImeWindow, appBelowImeTarget); - assertWindowHigher(appAboveImeTarget, mImeWindow); - assertWindowHigher(mImeWindow, mChildAppWindowAbove); - assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mNavBarWindow, mImeWindow); - assertWindowHigher(mStatusBarWindow, mImeWindow); - - // And, IME dialogs should always have an higher layer than the IME. - assertWindowHigher(mImeDialogWindow, mImeWindow); - } - - @Test - public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception { - final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY, - mDisplayContent, "imeSystemOverlayTarget", - true /* ownerCanAddInternalSystemWindow */); - - sWm.mInputMethodTarget = imeSystemOverlayTarget; - mDisplayContent.assignChildLayers(mTransaction); - - // The IME target base layer is higher than all window except for the nav bar window, so the - // IME should be above all windows except for the nav bar. - assertWindowHigher(mImeWindow, imeSystemOverlayTarget); - assertWindowHigher(mImeWindow, mChildAppWindowAbove); - assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mImeWindow, mDockedDividerWindow); - - // The IME has a higher base layer than the status bar so we may expect it to go - // above the status bar once they are both in the Non-App layer, as past versions of this - // test enforced. However this seems like the wrong behavior unless the status bar is the - // IME target. - assertWindowHigher(mNavBarWindow, mImeWindow); - assertWindowHigher(mStatusBarWindow, mImeWindow); - - // And, IME dialogs should always have an higher layer than the IME. - assertWindowHigher(mImeDialogWindow, mImeWindow); - } - - @Test - public void testAssignWindowLayers_ForStatusBarImeTarget() throws Exception { - sWm.mInputMethodTarget = mStatusBarWindow; - mDisplayContent.assignChildLayers(mTransaction); - - assertWindowHigher(mImeWindow, mChildAppWindowAbove); - assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mImeWindow, mDockedDividerWindow); - assertWindowHigher(mImeWindow, mStatusBarWindow); - - // And, IME dialogs should always have an higher layer than the IME. - assertWindowHigher(mImeDialogWindow, mImeWindow); - } - - @Test - public void testStackLayers() throws Exception { - final WindowState anyWindow1 = createWindow("anyWindow"); - final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED, - ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, - "pinnedStackWindow"); - final WindowState dockedStackWindow = createWindowOnStack(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, - mDisplayContent, "dockedStackWindow"); - final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, - mDisplayContent, "assistantStackWindow"); - final WindowState homeActivityWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_HOME, TYPE_BASE_APPLICATION, - mDisplayContent, "homeActivityWindow"); - final WindowState anyWindow2 = createWindow("anyWindow2"); - - mDisplayContent.assignChildLayers(mTransaction); - - assertWindowHigher(dockedStackWindow, homeActivityWindow); - assertWindowHigher(assistantStackWindow, homeActivityWindow); - assertWindowHigher(pinnedStackWindow, homeActivityWindow); - assertWindowHigher(anyWindow1, homeActivityWindow); - assertWindowHigher(anyWindow2, homeActivityWindow); - assertWindowHigher(pinnedStackWindow, anyWindow1); - assertWindowHigher(pinnedStackWindow, anyWindow2); - assertWindowHigher(pinnedStackWindow, dockedStackWindow); - assertWindowHigher(pinnedStackWindow, assistantStackWindow); - } - - @Test - public void testAssignWindowLayers_ForSysUiPanels() throws Exception { - final WindowState navBarPanel = - createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel"); - final WindowState statusBarPanel = - createWindow(null, TYPE_STATUS_BAR_PANEL, mDisplayContent, "StatusBarPanel"); - final WindowState statusBarSubPanel = - createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel"); - mDisplayContent.assignChildLayers(mTransaction); - - // Ime should be above all app windows and below system windows if it is targeting an app - // window. - assertWindowHigher(navBarPanel, mNavBarWindow); - assertWindowHigher(statusBarPanel, mStatusBarWindow); - assertWindowHigher(statusBarSubPanel, statusBarPanel); - } - - @Test - public void testAssignWindowLayers_ForNegativelyZOrderedSubtype() throws Exception { - // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA - // then we can drop all negative layering on the windowing side. - - final WindowState anyWindow = createWindow("anyWindow"); - final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent, - "TypeApplicationMediaChild"); - final WindowState mediaOverlayChild = createWindow(anyWindow, TYPE_APPLICATION_MEDIA_OVERLAY, - mDisplayContent, "TypeApplicationMediaOverlayChild"); - - mDisplayContent.assignChildLayers(mTransaction); - - assertWindowHigher(anyWindow, mediaOverlayChild); - assertWindowHigher(mediaOverlayChild, child); - } - - @Test - public void testDockedDividerPosition() throws Exception { - final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED, - ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, - "pinnedStackWindow"); - final WindowState splitScreenWindow = createWindowOnStack(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, - mDisplayContent, "splitScreenWindow"); - final WindowState splitScreenSecondaryWindow = createWindowOnStack(null, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, - TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); - final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, - mDisplayContent, "assistantStackWindow"); - - mDisplayContent.assignChildLayers(mTransaction); - - assertWindowHigher(mDockedDividerWindow, splitScreenWindow); - assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow); - assertWindowHigher(pinnedStackWindow, mDockedDividerWindow); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java deleted file mode 100644 index 40a10e04c893..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_180; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; - -import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; - -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.PointF; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ErrorCollector; - -public class CoordinateTransformsTest { - - private static final int W = 200; - private static final int H = 400; - - private final Matrix mMatrix = new Matrix(); - - @Rule - public final ErrorCollector mErrorCollector = new ErrorCollector(); - - @Before - public void setUp() throws Exception { - mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef); - } - - @Test - public void transformPhysicalToLogicalCoordinates_rot0() throws Exception { - transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix); - assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX)); - } - - @Test - public void transformPhysicalToLogicalCoordinates_rot90() throws Exception { - transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix); - - checkDevicePoint(0, 0).mapsToLogicalPoint(0, W); - checkDevicePoint(W, H).mapsToLogicalPoint(H, 0); - } - - @Test - public void transformPhysicalToLogicalCoordinates_rot180() throws Exception { - transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix); - - checkDevicePoint(0, 0).mapsToLogicalPoint(W, H); - checkDevicePoint(W, H).mapsToLogicalPoint(0, 0); - } - - @Test - public void transformPhysicalToLogicalCoordinates_rot270() throws Exception { - transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix); - - checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0); - checkDevicePoint(W, H).mapsToLogicalPoint(0, W); - } - - private DevicePointAssertable checkDevicePoint(int x, int y) { - final Point devicePoint = new Point(x, y); - final float[] fs = new float[] {x, y}; - mMatrix.mapPoints(fs); - final PointF transformedPoint = new PointF(fs[0], fs[1]); - - return (expectedX, expectedY) -> { - mErrorCollector.checkThat("t(" + devicePoint + ")", - transformedPoint, is(new PointF(expectedX, expectedY))); - }; - } - - public interface DevicePointAssertable { - void mapsToLogicalPoint(int x, int y); - } -} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java deleted file mode 100644 index d0f0fe315bcf..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.wm.utils; - -import static junit.framework.Assert.assertEquals; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Pair; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class InsetUtilsTest { - - @Test - public void testAdd() throws Exception { - final Rect rect1 = new Rect(10, 20, 30, 40); - final Rect rect2 = new Rect(50, 60, 70, 80); - InsetUtils.addInsets(rect1, rect2); - assertEquals(new Rect(60, 80, 100, 120), rect1); - } -} - diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java deleted file mode 100644 index 6bbc7eb56688..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/utils/RotationCacheTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - -import static android.util.Pair.create; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; - -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.FlakyTest; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Pair; - -import com.android.server.wm.utils.RotationCache.RotationDependentComputation; - -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@FlakyTest(bugId = 74078662) -@Presubmit -public class RotationCacheTest { - - private RotationCache> mCache; - private boolean mComputationCalled; - - @Before - public void setUp() throws Exception { - mComputationCalled = false; - mCache = new RotationCache<>((o, rot) -> { - mComputationCalled = true; - return create(o, rot); - }); - } - - @Test - public void getOrCompute_computes() throws Exception { - assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0))); - assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); - assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2))); - assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3))); - } - - @Test - public void getOrCompute_sameParam_sameRot_hitsCache() throws Exception { - assertNotNull(mCache.getOrCompute("hello", 1)); - - mComputationCalled = false; - assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); - assertThat(mComputationCalled, is(false)); - } - - @Test - public void getOrCompute_sameParam_hitsCache_forAllRots() throws Exception { - assertNotNull(mCache.getOrCompute("hello", 3)); - assertNotNull(mCache.getOrCompute("hello", 2)); - assertNotNull(mCache.getOrCompute("hello", 1)); - assertNotNull(mCache.getOrCompute("hello", 0)); - - mComputationCalled = false; - assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); - assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0))); - assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2))); - assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3))); - assertThat(mComputationCalled, is(false)); - } - - @Test - public void getOrCompute_changingParam_recomputes() throws Exception { - assertNotNull(mCache.getOrCompute("hello", 1)); - - assertThat(mCache.getOrCompute("world", 1), equalTo(create("world", 1))); - } - - @Test - public void getOrCompute_changingParam_clearsCacheForDifferentRots() throws Exception { - assertNotNull(mCache.getOrCompute("hello", 1)); - assertNotNull(mCache.getOrCompute("world", 2)); - - mComputationCalled = false; - assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); - assertThat(mComputationCalled, is(true)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java deleted file mode 100644 index f7addf6c77f9..000000000000 --- a/services/tests/servicestests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.utils; - - -import static android.view.DisplayCutout.NO_CUTOUT; -import static android.view.DisplayCutout.fromBoundingRect; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.util.Size; -import android.view.DisplayCutout; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; - -/** - * Tests for {@link WmDisplayCutout} - * - * Run with: atest WmDisplayCutoutTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class WmDisplayCutoutTest { - - private final DisplayCutout mCutoutTop = new DisplayCutout( - new Rect(0, 100, 0, 0), - Arrays.asList(new Rect(50, 0, 75, 100))); - - @Test - public void calculateRelativeTo_top() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 100, 20), 200, 400) - .calculateRelativeTo(new Rect(5, 5, 95, 195)); - - assertEquals(new Rect(0, 15, 0, 0), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void calculateRelativeTo_left() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 20, 100), 400, 200) - .calculateRelativeTo(new Rect(5, 5, 195, 95)); - - assertEquals(new Rect(15, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void calculateRelativeTo_bottom() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 180, 100, 200), 100, 200) - .calculateRelativeTo(new Rect(5, 5, 95, 195)); - - assertEquals(new Rect(0, 0, 0, 15), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void calculateRelativeTo_right() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(180, 0, 200, 100), 200, 100) - .calculateRelativeTo(new Rect(5, 5, 195, 95)); - - assertEquals(new Rect(0, 0, 15, 0), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void calculateRelativeTo_bounds() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 100, 20), 200, 400) - .calculateRelativeTo(new Rect(5, 10, 95, 180)); - - assertEquals(new Rect(-5, -10, 95, 10), cutout.getDisplayCutout().getBounds().getBounds()); - } - - @Test - public void computeSafeInsets_top() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 100, 20), 200, 400); - - assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void computeSafeInsets_left() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 0, 20, 100), 400, 200); - - assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void computeSafeInsets_bottom() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(0, 180, 100, 200), 100, 200); - - assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void computeSafeInsets_right() { - WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( - fromBoundingRect(180, 0, 200, 100), 200, 100); - - assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets()); - } - - @Test - public void computeSafeInsets_bounds() { - DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000, - 2000).getDisplayCutout(); - - assertEquals(mCutoutTop.getBounds().getBounds(), - cutout.getBounds().getBounds()); - } - - @Test - public void test_equals() { - assertEquals(new WmDisplayCutout(NO_CUTOUT, null), new WmDisplayCutout(NO_CUTOUT, null)); - assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), - new WmDisplayCutout(mCutoutTop, new Size(1, 2))); - - assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), - new WmDisplayCutout(mCutoutTop, new Size(5, 6))); - assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), - new WmDisplayCutout(NO_CUTOUT, new Size(1, 2))); - } - - @Test - public void test_hashCode() { - assertEquals(new WmDisplayCutout(NO_CUTOUT, null).hashCode(), - new WmDisplayCutout(NO_CUTOUT, null).hashCode()); - assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode(), - new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode()); - } -} \ No newline at end of file diff --git a/services/tests/wmtests/Android.mk b/services/tests/wmtests/Android.mk index 0f8b18ab92cf..cd289740ea1e 100644 --- a/services/tests/wmtests/Android.mk +++ b/services/tests/wmtests/Android.mk @@ -14,9 +14,14 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, ../servicestests/utils) LOCAL_STATIC_JAVA_LIBRARIES := \ - androidx-test \ + frameworks-base-testutils \ + services.core \ + android-support-test \ mockito-target-minus-junit4 \ platform-test-annotations \ + truth-prebuilt \ + testables \ + ub-uiautomator \ LOCAL_JAVA_LIBRARIES := \ android.test.mock \ diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 1fb947309028..7e4bd42c8c55 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -22,10 +22,30 @@ android:minSdkVersion="1" android:targetSdkVersion="28" /> - + + + + + + + + + + + + + + + + + + + + + diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml index 2717ef901216..38a62ae9bbe3 100644 --- a/services/tests/wmtests/AndroidTest.xml +++ b/services/tests/wmtests/AndroidTest.xml @@ -24,9 +24,9 @@ diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java new file mode 100644 index 000000000000..089dc61fc250 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE; +import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.am.LaunchParamsController.LaunchParams; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for exercising resizing bounds due to activity options. + * + * Build/Install/Run: + * atest WmTests:ActivityLaunchParamsModifierTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityLaunchParamsModifierTests extends ActivityTestsBase { + private ActivityLaunchParamsModifier mModifier; + private ActivityManagerService mService; + private ActivityStack mStack; + private TaskRecord mTask; + private ActivityRecord mActivity; + + private LaunchParams mCurrent; + private LaunchParams mResult; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mService = createActivityManagerService(); + mModifier = new ActivityLaunchParamsModifier(mService.mStackSupervisor); + mCurrent = new LaunchParams(); + mResult = new LaunchParams(); + + + mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + mActivity = new ActivityBuilder(mService).setTask(mTask).build(); + } + + + @Test + public void testSkippedInvocations() throws Exception { + // No specified activity should be ignored + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + null /*activity*/, null /*source*/, null /*options*/, mCurrent, mResult)); + + // No specified activity options should be ignored + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, null /*options*/, mCurrent, mResult)); + + // launch bounds specified should be ignored. + final ActivityOptions options = ActivityOptions.makeBasic(); + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + + // Non-resizeable records should be ignored + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + assertFalse(mActivity.isResizeable()); + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + + // make record resizeable + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + assertTrue(mActivity.isResizeable()); + + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + + // Does not support freeform + mService.mSupportsFreeformWindowManagement = false; + assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + + mService.mSupportsFreeformWindowManagement = true; + options.setLaunchBounds(new Rect()); + assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); + + // Invalid bounds + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + options.setLaunchBounds(new Rect(0, 0, -1, -1)); + assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + + // Valid bounds should cause the positioner to be applied. + options.setLaunchBounds(new Rect(0, 0, 100, 100)); + assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + } + + @Test + public void testBoundsExtraction() throws Exception { + // Make activity resizeable and enable freeform mode. + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + mService.mSupportsFreeformWindowManagement = true; + + ActivityOptions options = ActivityOptions.makeBasic(); + final Rect proposedBounds = new Rect(20, 30, 45, 40); + options.setLaunchBounds(proposedBounds); + + assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/, + mActivity, null /*source*/, options /*options*/, mCurrent, mResult)); + assertEquals(mResult.mBounds, proposedBounds); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/wmtests/src/com/android/server/am/ActivityManagerInternalTest.java new file mode 100644 index 000000000000..f393d14e1cfb --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityManagerInternalTest.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.ActivityManagerInternal; +import android.os.SystemClock; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link ActivityManagerInternal}. + * + * Build/Install/Run: + * atest WmTests:ActivityManagerInternalTest + */ +@RunWith(AndroidJUnit4.class) +public class ActivityManagerInternalTest { + private static final int TEST_UID1 = 111; + private static final int TEST_UID2 = 112; + + private static final long TEST_PROC_STATE_SEQ1 = 1111; + private static final long TEST_PROC_STATE_SEQ2 = 1112; + private static final long TEST_PROC_STATE_SEQ3 = 1113; + + @Mock private ActivityManagerService.Injector mMockInjector; + + private ActivityManagerService mAms; + private ActivityManagerInternal mAmi; + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mAms = new ActivityManagerService(mMockInjector); + mAmi = mAms.new LocalService(); + } + + @MediumTest + @Test + public void testNotifyNetworkPolicyRulesUpdated() throws Exception { + // Check there is no crash when there are no active uid records. + mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, TEST_PROC_STATE_SEQ1); + + // Notify that network policy rules are updated for TEST_UID1 and verify that + // UidRecord.lastNetworkUpdateProcStateSeq is updated and any blocked threads are notified. + verifyNetworkUpdatedProcStateSeq( + TEST_PROC_STATE_SEQ2, // curProcStateSeq + TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq + TEST_PROC_STATE_SEQ2, // procStateSeq to notify + true); // expectNotify + + // Notify that network policy rules are updated for TEST_UID1 with already handled + // procStateSeq and verify that there is no notify call. + verifyNetworkUpdatedProcStateSeq( + TEST_PROC_STATE_SEQ1, // curProcStateSeq + TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq + TEST_PROC_STATE_SEQ1, // procStateSeq to notify + false); // expectNotify + + // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older + // than it's UidRecord.curProcStateSeq and verify that there is no notify call. + verifyNetworkUpdatedProcStateSeq( + TEST_PROC_STATE_SEQ3, // curProcStateSeq + TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq + TEST_PROC_STATE_SEQ2, // procStateSeq to notify + false); // expectNotify + } + + private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq, + long lastNetworkUpdatedProcStateSeq, long expectedProcStateSeq, boolean expectNotify) + throws Exception { + final UidRecord record1 = addActiveUidRecord(TEST_UID1, curProcStateSeq, + lastNetworkUpdatedProcStateSeq); + final UidRecord record2 = addActiveUidRecord(TEST_UID2, curProcStateSeq, + lastNetworkUpdatedProcStateSeq); + + final CustomThread thread1 = new CustomThread(record1.networkStateLock); + thread1.startAndWait("Unexpected state for " + record1); + final CustomThread thread2 = new CustomThread(record2.networkStateLock); + thread2.startAndWait("Unexpected state for " + record2); + + mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, expectedProcStateSeq); + assertEquals(record1 + " should be updated", + expectedProcStateSeq, record1.lastNetworkUpdatedProcStateSeq); + assertEquals(record2 + " should not be updated", + lastNetworkUpdatedProcStateSeq, record2.lastNetworkUpdatedProcStateSeq); + + if (expectNotify) { + thread1.assertTerminated("Unexpected state for " + record1); + assertTrue("Threads waiting for network should be notified: " + record1, + thread1.mNotified); + } else { + thread1.assertWaiting("Unexpected state for " + record1); + thread1.interrupt(); + } + thread2.assertWaiting("Unexpected state for " + record2); + thread2.interrupt(); + + mAms.mActiveUids.clear(); + } + + private UidRecord addActiveUidRecord(int uid, long curProcStateSeq, + long lastNetworkUpdatedProcStateSeq) { + final UidRecord record = new UidRecord(uid); + record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; + record.curProcStateSeq = curProcStateSeq; + record.waitingForNetwork = true; + mAms.mActiveUids.put(uid, record); + return record; + } + + static class CustomThread extends Thread { + private static final long WAIT_TIMEOUT_MS = 1000; + private static final long WAIT_INTERVAL_MS = 100; + + private final Object mLock; + private Runnable mRunnable; + boolean mNotified; + + public CustomThread(Object lock) { + mLock = lock; + } + + public CustomThread(Object lock, Runnable runnable) { + super(runnable); + mLock = lock; + mRunnable = runnable; + } + + @Override + public void run() { + if (mRunnable != null) { + mRunnable.run(); + } else { + synchronized (mLock) { + try { + mLock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupted(); + } + } + } + mNotified = !Thread.interrupted(); + } + + public void startAndWait(String errMsg) throws Exception { + startAndWait(errMsg, false); + } + + public void startAndWait(String errMsg, boolean timedWaiting) throws Exception { + start(); + final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS; + final Thread.State stateToReach = timedWaiting + ? Thread.State.TIMED_WAITING : Thread.State.WAITING; + while (getState() != stateToReach + && SystemClock.elapsedRealtime() < endTime) { + Thread.sleep(WAIT_INTERVAL_MS); + } + if (timedWaiting) { + assertTimedWaiting(errMsg); + } else { + assertWaiting(errMsg); + } + } + + public void assertWaiting(String errMsg) { + assertEquals(errMsg, Thread.State.WAITING, getState()); + } + + public void assertTimedWaiting(String errMsg) { + assertEquals(errMsg, Thread.State.TIMED_WAITING, getState()); + } + + public void assertTerminated(String errMsg) throws Exception { + final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS; + while (getState() != Thread.State.TERMINATED + && SystemClock.elapsedRealtime() < endTime) { + Thread.sleep(WAIT_INTERVAL_MS); + } + assertEquals(errMsg, Thread.State.TERMINATED, getState()); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/wmtests/src/com/android/server/am/ActivityManagerServiceTest.java new file mode 100644 index 000000000000..c41197dca2d7 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; +import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.util.DebugUtils.valueToString; + +import static com.android.server.am.ActivityManagerInternalTest.CustomThread; +import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG; +import static com.android.server.am.ActivityManagerService.Injector; +import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK; +import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE; +import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.IApplicationThread; +import android.app.IUidObserver; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.support.test.filters.MediumTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.AppOpsService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +/** + * Test class for {@link ActivityManagerService}. + * + * Build/Install/Run: + * atest WmTests:ActivityManagerServiceTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ActivityManagerServiceTest { + private static final String TAG = ActivityManagerServiceTest.class.getSimpleName(); + + private static final int TEST_UID = 11111; + + private static final long TEST_PROC_STATE_SEQ1 = 555; + private static final long TEST_PROC_STATE_SEQ2 = 556; + + private static final int[] UID_RECORD_CHANGES = { + UidRecord.CHANGE_PROCSTATE, + UidRecord.CHANGE_GONE, + UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE, + UidRecord.CHANGE_IDLE, + UidRecord.CHANGE_ACTIVE + }; + + @Mock private Context mContext; + @Mock private AppOpsService mAppOpsService; + @Mock private PackageManager mPackageManager; + @Mock private BatteryStatsImpl mBatteryStatsImpl; + + private TestInjector mInjector; + private ActivityManagerService mAms; + private HandlerThread mHandlerThread; + private TestHandler mHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new TestHandler(mHandlerThread.getLooper()); + mInjector = new TestInjector(); + mAms = new ActivityManagerService(mInjector); + mAms.mWaitForNetworkTimeoutMs = 2000; + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + } + + @After + public void tearDown() { + mHandlerThread.quit(); + } + + @MediumTest + @Test + public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception { + + final UidRecord uidRec = addUidRecord(TEST_UID); + addUidRecord(TEST_UID + 1); + + // Uid state is not moving from background to foreground or vice versa. + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_TOP, // prevState + PROCESS_STATE_TOP, // curState + 0, // expectedGlobalCounter + 0, // exptectedCurProcStateSeq + NETWORK_STATE_NO_CHANGE, // expectedBlockState + false); // expectNotify + + // Uid state is moving from foreground to background. + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_FOREGROUND_SERVICE, // prevState + PROCESS_STATE_SERVICE, // curState + 1, // expectedGlobalCounter + 1, // exptectedCurProcStateSeq + NETWORK_STATE_UNBLOCK, // expectedBlockState + true); // expectNotify + + // Explicitly setting the seq counter for more verification. + mAms.mProcStateSeqCounter = 42; + + // Uid state is not moving from background to foreground or vice versa. + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState + PROCESS_STATE_IMPORTANT_FOREGROUND, // curState + 42, // expectedGlobalCounter + 1, // exptectedCurProcStateSeq + NETWORK_STATE_NO_CHANGE, // expectedBlockState + false); // expectNotify + + // Uid state is moving from background to foreground. + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_LAST_ACTIVITY, // prevState + PROCESS_STATE_TOP, // curState + 43, // expectedGlobalCounter + 43, // exptectedCurProcStateSeq + NETWORK_STATE_BLOCK, // expectedBlockState + false); // expectNotify + + // verify waiting threads are not notified. + uidRec.waitingForNetwork = false; + // Uid state is moving from foreground to background. + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_FOREGROUND_SERVICE, // prevState + PROCESS_STATE_SERVICE, // curState + 44, // expectedGlobalCounter + 44, // exptectedCurProcStateSeq + NETWORK_STATE_UNBLOCK, // expectedBlockState + false); // expectNotify + + // Verify when uid is not restricted, procStateSeq is not incremented. + uidRec.waitingForNetwork = true; + mInjector.setNetworkRestrictedForUid(false); + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState + PROCESS_STATE_TOP, // curState + 44, // expectedGlobalCounter + 44, // exptectedCurProcStateSeq + -1, // expectedBlockState, -1 to verify there are no interactions with main thread. + false); // expectNotify + + // Verify when waitForNetworkTimeout is 0, then procStateSeq is not incremented. + mAms.mWaitForNetworkTimeoutMs = 0; + mInjector.setNetworkRestrictedForUid(true); + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_TOP, // prevState + PROCESS_STATE_IMPORTANT_BACKGROUND, // curState + 44, // expectedGlobalCounter + 44, // exptectedCurProcStateSeq + -1, // expectedBlockState, -1 to verify there are no interactions with main thread. + false); // expectNotify + + // Verify when the uid doesn't have internet permission, then procStateSeq is not + // incremented. + uidRec.hasInternetPermission = false; + mAms.mWaitForNetworkTimeoutMs = 111; + mInjector.setNetworkRestrictedForUid(true); + verifySeqCounterAndInteractions(uidRec, + PROCESS_STATE_CACHED_ACTIVITY, // prevState + PROCESS_STATE_FOREGROUND_SERVICE, // curState + 44, // expectedGlobalCounter + 44, // exptectedCurProcStateSeq + -1, // expectedBlockState, -1 to verify there are no interactions with main thread. + false); // expectNotify + + // Verify procStateSeq is not incremented when the uid is not an application, regardless + // of the process state. + final int notAppUid = 111; + final UidRecord uidRec2 = addUidRecord(notAppUid); + verifySeqCounterAndInteractions(uidRec2, + PROCESS_STATE_CACHED_EMPTY, // prevState + PROCESS_STATE_TOP, // curState + 44, // expectedGlobalCounter + 0, // exptectedCurProcStateSeq + -1, // expectedBlockState, -1 to verify there are no interactions with main thread. + false); // expectNotify + } + + private UidRecord addUidRecord(int uid) { + final UidRecord uidRec = new UidRecord(uid); + uidRec.waitingForNetwork = true; + uidRec.hasInternetPermission = true; + mAms.mActiveUids.put(uid, uidRec); + + final ProcessRecord appRec = new ProcessRecord(null, mBatteryStatsImpl, + new ApplicationInfo(), TAG, uid); + appRec.thread = Mockito.mock(IApplicationThread.class); + mAms.mLruProcesses.add(appRec); + + return uidRec; + } + + private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState, + int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState, + boolean expectNotify) throws Exception { + CustomThread thread = new CustomThread(uidRec.networkStateLock); + thread.startAndWait("Unexpected state for " + uidRec); + + uidRec.setProcState = prevState; + uidRec.curProcState = curState; + mAms.incrementProcStateSeqAndNotifyAppsLocked(); + + assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter); + assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq); + + for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) { + final ProcessRecord app = mAms.mLruProcesses.get(i); + // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE. + if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) { + verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq); + } else { + verifyZeroInteractions(app.thread); + } + Mockito.reset(app.thread); + } + + if (expectNotify) { + thread.assertTerminated("Unexpected state for " + uidRec); + } else { + thread.assertWaiting("Unexpected state for " + uidRec); + thread.interrupt(); + } + } + + @Test + public void testBlockStateForUid() { + final UidRecord uidRec = new UidRecord(TEST_UID); + int expectedBlockState; + + final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s"; + Function errorMsg = (blockState) -> { + return String.format(errorTemplate, + valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState), + valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState), + valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState)); + }; + + // No change in uid state + uidRec.setProcState = PROCESS_STATE_RECEIVER; + uidRec.curProcState = PROCESS_STATE_RECEIVER; + expectedBlockState = NETWORK_STATE_NO_CHANGE; + assertEquals(errorMsg.apply(expectedBlockState), + expectedBlockState, mAms.getBlockStateForUid(uidRec)); + + // Foreground to foreground + uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE; + uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + expectedBlockState = NETWORK_STATE_NO_CHANGE; + assertEquals(errorMsg.apply(expectedBlockState), + expectedBlockState, mAms.getBlockStateForUid(uidRec)); + + // Background to background + uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY; + uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY; + expectedBlockState = NETWORK_STATE_NO_CHANGE; + assertEquals(errorMsg.apply(expectedBlockState), + expectedBlockState, mAms.getBlockStateForUid(uidRec)); + + // Background to background + uidRec.setProcState = PROCESS_STATE_NONEXISTENT; + uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY; + expectedBlockState = NETWORK_STATE_NO_CHANGE; + assertEquals(errorMsg.apply(expectedBlockState), + expectedBlockState, mAms.getBlockStateForUid(uidRec)); + + // Background to foreground + uidRec.setProcState = PROCESS_STATE_SERVICE; + uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE; + expectedBlockState = NETWORK_STATE_BLOCK; + assertEquals(errorMsg.apply(expectedBlockState), + expectedBlockState, mAms.getBlockStateForUid(uidRec)); + + // Foreground to background + uidRec.setProcState = PROCESS_STATE_TOP; + uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY; + expectedBlockState = NETWORK_STATE_UNBLOCK; + assertEquals(errorMsg.apply(expectedBlockState), + expectedBlockState, mAms.getBlockStateForUid(uidRec)); + } + + /** + * This test verifies that process state changes are dispatched to observers based on the + * changes they wanted to listen (this is specified when registering the observer). + */ + @Test + public void testDispatchUids_dispatchNeededChanges() throws RemoteException { + when(mAppOpsService.noteOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null)) + .thenReturn(AppOpsManager.MODE_ALLOWED); + + final int[] changesToObserve = { + ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.UID_OBSERVER_GONE, + ActivityManager.UID_OBSERVER_IDLE, + ActivityManager.UID_OBSERVER_ACTIVE, + ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE + | ActivityManager.UID_OBSERVER_ACTIVE | ActivityManager.UID_OBSERVER_IDLE + }; + final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length]; + for (int i = 0; i < observers.length; ++i) { + observers[i] = Mockito.mock(IUidObserver.Stub.class); + when(observers[i].asBinder()).thenReturn((IBinder) observers[i]); + mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */, + ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */); + + // When we invoke AMS.registerUidObserver, there are some interactions with observers[i] + // mock in RemoteCallbackList class. We don't want to test those interactions and + // at the same time, we don't want those to interfere with verifyNoMoreInteractions. + // So, resetting the mock here. + Mockito.reset(observers[i]); + } + + // Add pending uid records each corresponding to a different change type UidRecord.CHANGE_* + final int[] changesForPendingUidRecords = UID_RECORD_CHANGES; + + final int[] procStatesForPendingUidRecords = { + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + ActivityManager.PROCESS_STATE_NONEXISTENT, + ActivityManager.PROCESS_STATE_CACHED_EMPTY, + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, + ActivityManager.PROCESS_STATE_TOP + }; + final Map changeItems = new HashMap<>(); + for (int i = 0; i < changesForPendingUidRecords.length; ++i) { + final UidRecord.ChangeItem pendingChange = new UidRecord.ChangeItem(); + pendingChange.change = changesForPendingUidRecords[i]; + pendingChange.uid = i; + pendingChange.processState = procStatesForPendingUidRecords[i]; + pendingChange.procStateSeq = i; + changeItems.put(changesForPendingUidRecords[i], pendingChange); + mAms.mPendingUidChanges.add(pendingChange); + } + + mAms.dispatchUidsChanged(); + // Verify the required changes have been dispatched to observers. + for (int i = 0; i < observers.length; ++i) { + final int changeToObserve = changesToObserve[i]; + final IUidObserver observerToTest = observers[i]; + if ((changeToObserve & ActivityManager.UID_OBSERVER_IDLE) != 0) { + // Observer listens to uid idle changes, so change items corresponding to + // UidRecord.CHANGE_IDLE or UidRecord.CHANGE_IDLE_GONE needs to be + // delivered to this observer. + final int[] changesToVerify = { + UidRecord.CHANGE_IDLE, + UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE + }; + verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, + (observer, changeItem) -> { + verify(observer).onUidIdle(changeItem.uid, changeItem.ephemeral); + }); + } + if ((changeToObserve & ActivityManager.UID_OBSERVER_ACTIVE) != 0) { + // Observer listens to uid active changes, so change items corresponding to + // UidRecord.CHANGE_ACTIVE needs to be delivered to this observer. + final int[] changesToVerify = { UidRecord.CHANGE_ACTIVE }; + verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, + (observer, changeItem) -> { + verify(observer).onUidActive(changeItem.uid); + }); + } + if ((changeToObserve & ActivityManager.UID_OBSERVER_GONE) != 0) { + // Observer listens to uid gone changes, so change items corresponding to + // UidRecord.CHANGE_GONE or UidRecord.CHANGE_IDLE_GONE needs to be + // delivered to this observer. + final int[] changesToVerify = { + UidRecord.CHANGE_GONE, + UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE + }; + verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, + (observer, changeItem) -> { + verify(observer).onUidGone(changeItem.uid, changeItem.ephemeral); + }); + } + if ((changeToObserve & ActivityManager.UID_OBSERVER_PROCSTATE) != 0) { + // Observer listens to uid procState changes, so change items corresponding to + // UidRecord.CHANGE_PROCSTATE or UidRecord.CHANGE_IDLE or UidRecord.CHANGE_ACTIVE + // needs to be delivered to this observer. + final int[] changesToVerify = { + UidRecord.CHANGE_PROCSTATE, + UidRecord.CHANGE_ACTIVE, + UidRecord.CHANGE_IDLE + }; + verifyObserverReceivedChanges(observerToTest, changesToVerify, changeItems, + (observer, changeItem) -> { + verify(observer).onUidStateChanged(changeItem.uid, + changeItem.processState, changeItem.procStateSeq); + }); + } + // Verify there are no other callbacks for this observer. + verifyNoMoreInteractions(observerToTest); + } + } + + private interface ObserverChangesVerifier { + void verify(IUidObserver observer, UidRecord.ChangeItem changeItem) throws RemoteException; + } + + private void verifyObserverReceivedChanges(IUidObserver observer, int[] changesToVerify, + Map changeItems, ObserverChangesVerifier verifier) + throws RemoteException { + for (int change : changesToVerify) { + final UidRecord.ChangeItem changeItem = changeItems.get(change); + verifier.verify(observer, changeItem); + } + } + + /** + * This test verifies that process state changes are dispatched to observers only when they + * change across the cutpoint (this is specified when registering the observer). + */ + @Test + public void testDispatchUidChanges_procStateCutpoint() throws RemoteException { + final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class); + + when(observer.asBinder()).thenReturn((IBinder) observer); + mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */, + ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */); + // When we invoke AMS.registerUidObserver, there are some interactions with observer + // mock in RemoteCallbackList class. We don't want to test those interactions and + // at the same time, we don't want those to interfere with verifyNoMoreInteractions. + // So, resetting the mock here. + Mockito.reset(observer); + + final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem(); + changeItem.uid = TEST_UID; + changeItem.change = UidRecord.CHANGE_PROCSTATE; + changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; + changeItem.procStateSeq = 111; + mAms.mPendingUidChanges.add(changeItem); + mAms.dispatchUidsChanged(); + // First process state message is always delivered regardless of whether the process state + // change is above or below the cutpoint (PROCESS_STATE_SERVICE). + verify(observer).onUidStateChanged(TEST_UID, + changeItem.processState, changeItem.procStateSeq); + verifyNoMoreInteractions(observer); + + changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER; + mAms.mPendingUidChanges.add(changeItem); + mAms.dispatchUidsChanged(); + // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and + // the current process state change is also below cutpoint, so no callback will be invoked. + verifyNoMoreInteractions(observer); + + changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + mAms.mPendingUidChanges.add(changeItem); + mAms.dispatchUidsChanged(); + // Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and + // the current process state change is above cutpoint, so callback will be invoked with the + // current process state change. + verify(observer).onUidStateChanged(TEST_UID, + changeItem.processState, changeItem.procStateSeq); + verifyNoMoreInteractions(observer); + + changeItem.processState = ActivityManager.PROCESS_STATE_TOP; + mAms.mPendingUidChanges.add(changeItem); + mAms.dispatchUidsChanged(); + // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and + // the current process state change is also above cutpoint, so no callback will be invoked. + verifyNoMoreInteractions(observer); + + changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + mAms.mPendingUidChanges.add(changeItem); + mAms.dispatchUidsChanged(); + // Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and + // the current process state change is below cutpoint, so callback will be invoked with the + // current process state change. + verify(observer).onUidStateChanged(TEST_UID, + changeItem.processState, changeItem.procStateSeq); + verifyNoMoreInteractions(observer); + } + + /** + * This test verifies that {@link ActivityManagerService#mValidateUids} which is a + * part of dumpsys is correctly updated. + */ + @Test + public void testDispatchUidChanges_validateUidsUpdated() { + final int[] changesForPendingItems = UID_RECORD_CHANGES; + + final int[] procStatesForPendingItems = { + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + ActivityManager.PROCESS_STATE_CACHED_EMPTY, + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, + ActivityManager.PROCESS_STATE_SERVICE, + ActivityManager.PROCESS_STATE_RECEIVER + }; + final ArrayList pendingItemsForUids + = new ArrayList<>(changesForPendingItems.length); + for (int i = 0; i < changesForPendingItems.length; ++i) { + final UidRecord.ChangeItem item = new UidRecord.ChangeItem(); + item.uid = i; + item.change = changesForPendingItems[i]; + item.processState = procStatesForPendingItems[i]; + pendingItemsForUids.add(i, item); + } + + // Verify that when there no observers listening to uid state changes, then there will + // be no changes to validateUids. + mAms.mPendingUidChanges.addAll(pendingItemsForUids); + mAms.dispatchUidsChanged(); + assertEquals("No observers registered, so validateUids should be empty", + 0, mAms.mValidateUids.size()); + + final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class); + when(observer.asBinder()).thenReturn((IBinder) observer); + mAms.registerUidObserver(observer, 0, 0, null); + // Verify that when observers are registered, then validateUids is correctly updated. + mAms.mPendingUidChanges.addAll(pendingItemsForUids); + mAms.dispatchUidsChanged(); + for (int i = 0; i < pendingItemsForUids.size(); ++i) { + final UidRecord.ChangeItem item = pendingItemsForUids.get(i); + final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid); + if ((item.change & UidRecord.CHANGE_GONE) != 0) { + assertNull("validateUidRecord should be null since the change is either " + + "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord); + } else { + assertNotNull("validateUidRecord should not be null since the change is neither " + + "CHANGE_GONE nor CHANGE_GONE_IDLE", validateUidRecord); + assertEquals("processState: " + item.processState + " curProcState: " + + validateUidRecord.curProcState + " should have been equal", + item.processState, validateUidRecord.curProcState); + assertEquals("processState: " + item.processState + " setProcState: " + + validateUidRecord.curProcState + " should have been equal", + item.processState, validateUidRecord.setProcState); + if (item.change == UidRecord.CHANGE_IDLE) { + assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE", + validateUidRecord.idle); + } else if (item.change == UidRecord.CHANGE_ACTIVE) { + assertFalse("UidRecord.idle should be updated to false for CHANGE_ACTIVE", + validateUidRecord.idle); + } + } + } + + // Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it + // will be removed from validateUids. + assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size()); + for (int i = 0; i < pendingItemsForUids.size(); ++i) { + final UidRecord.ChangeItem item = pendingItemsForUids.get(i); + // Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd + // distribution for this assignment. + item.change = (i % 2) == 0 ? (UidRecord.CHANGE_GONE | UidRecord.CHANGE_IDLE) + : UidRecord.CHANGE_GONE; + } + mAms.mPendingUidChanges.addAll(pendingItemsForUids); + mAms.dispatchUidsChanged(); + assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids, + 0, mAms.mValidateUids.size()); + } + + @Test + public void testEnqueueUidChangeLocked_procStateSeqUpdated() { + final UidRecord uidRecord = new UidRecord(TEST_UID); + uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1; + + // Verify with no pending changes for TEST_UID. + verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1); + + // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as + // expected. + final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem(); + uidRecord.pendingChange = changeItem; + uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2; + verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2); + } + + @Test + public void testEnqueueUidChangeLocked_nullUidRecord() { + // Use "null" uidRecord to make sure there is no crash. + mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE); + } + + private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) { + // Test enqueueUidChangeLocked with every UidRecord.CHANGE_* + for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) { + final int changeToDispatch = UID_RECORD_CHANGES[i]; + // Reset lastProcStateSeqDispatchToObservers after every test. + uidRecord.lastDispatchedProcStateSeq = 0; + mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch); + // Verify there is no effect on curProcStateSeq. + assertEquals(curProcstateSeq, uidRecord.curProcStateSeq); + if ((changeToDispatch & UidRecord.CHANGE_GONE) != 0) { + // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that + // lastProcStateSeqDispatchedToObservers is not updated. + assertNotEquals(uidRecord.curProcStateSeq, + uidRecord.lastDispatchedProcStateSeq); + } else { + // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that + // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq. + assertEquals(uidRecord.curProcStateSeq, + uidRecord.lastDispatchedProcStateSeq); + } + } + } + + @MediumTest + @Test + public void testEnqueueUidChangeLocked_dispatchUidsChanged() { + final UidRecord uidRecord = new UidRecord(TEST_UID); + final int expectedProcState = PROCESS_STATE_SERVICE; + uidRecord.setProcState = expectedProcState; + uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1; + + // Test with no pending uid records. + for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) { + final int changeToDispatch = UID_RECORD_CHANGES[i]; + + // Reset the current state + mHandler.reset(); + uidRecord.pendingChange = null; + mAms.mPendingUidChanges.clear(); + + mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch); + + // Verify that UidRecord.pendingChange is updated correctly. + assertNotNull(uidRecord.pendingChange); + assertEquals(TEST_UID, uidRecord.pendingChange.uid); + assertEquals(expectedProcState, uidRecord.pendingChange.processState); + assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq); + + // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler. + mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG); + } + } + + @MediumTest + @Test + public void testWaitForNetworkStateUpdate() throws Exception { + // Check there is no crash when there is no UidRecord for myUid + mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1); + + // Verify there is no waiting when UidRecord.curProcStateSeq is greater than + // the procStateSeq in the request to wait. + verifyWaitingForNetworkStateUpdate( + TEST_PROC_STATE_SEQ1, // curProcStateSeq + TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq + TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq + TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait + false); // expectWait + + // Verify there is no waiting when the procStateSeq in the request to wait is + // not dispatched to NPMS. + verifyWaitingForNetworkStateUpdate( + TEST_PROC_STATE_SEQ1, // curProcStateSeq + TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq + TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq + TEST_PROC_STATE_SEQ1, // procStateSeqToWait + false); // expectWait + + // Verify there is not waiting when the procStateSeq in the request already has + // an updated network state. + verifyWaitingForNetworkStateUpdate( + TEST_PROC_STATE_SEQ1, // curProcStateSeq + TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq + TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq + TEST_PROC_STATE_SEQ1, // procStateSeqToWait + false); // expectWait + + // Verify waiting for network works + verifyWaitingForNetworkStateUpdate( + TEST_PROC_STATE_SEQ1, // curProcStateSeq + TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq + TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq + TEST_PROC_STATE_SEQ1, // procStateSeqToWait + true); // expectWait + } + + private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq, + long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq, + final long procStateSeqToWait, boolean expectWait) throws Exception { + final UidRecord record = new UidRecord(Process.myUid()); + record.curProcStateSeq = curProcStateSeq; + record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq; + record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq; + mAms.mActiveUids.put(Process.myUid(), record); + + CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() { + @Override + public void run() { + mAms.waitForNetworkStateUpdate(procStateSeqToWait); + } + }); + final String errMsg = "Unexpected state for " + record; + if (expectWait) { + thread.startAndWait(errMsg, true); + thread.assertTimedWaiting(errMsg); + synchronized (record.networkStateLock) { + record.networkStateLock.notifyAll(); + } + thread.assertTerminated(errMsg); + assertTrue(thread.mNotified); + assertFalse(record.waitingForNetwork); + } else { + thread.start(); + thread.assertTerminated(errMsg); + } + + mAms.mActiveUids.clear(); + } + + private class TestHandler extends Handler { + private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec + private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec + + private Set mMsgsHandled = new HashSet<>(); + + TestHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + mMsgsHandled.add(msg.what); + } + + public void waitForMessage(int msg) { + final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS; + while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) { + SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS); + } + if (!mMsgsHandled.contains(msg)) { + fail("Timed out waiting for the message to be handled, msg: " + msg); + } + } + + public void reset() { + mMsgsHandled.clear(); + } + } + + private class TestInjector extends Injector { + private boolean mRestricted = true; + + @Override + public Context getContext() { + return mContext; + } + + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandler; + } + + @Override + public boolean isNetworkRestrictedForUid(int uid) { + return mRestricted; + } + + public void setNetworkRestrictedForUid(boolean restricted) { + mRestricted = restricted; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/wmtests/src/com/android/server/am/ActivityManagerTest.java new file mode 100644 index 000000000000..a2f942e7f96e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityManagerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.test.AndroidTestCase; + +import java.util.List; + +/** + * Tests for {@link ActivityManager}. + * + * Build/Install/Run: + * atest WmTests:ActivityManagerTest + */ +public class ActivityManagerTest extends AndroidTestCase { + + IActivityManager service; + @Override + public void setUp() throws Exception { + super.setUp(); + service = ActivityManager.getService(); + } + + public void testTaskIdsForRunningUsers() throws RemoteException { + for(int userId : service.getRunningUserIds()) { + testTaskIdsForUser(userId); + } + } + + private void testTaskIdsForUser(int userId) throws RemoteException { + List recentTasks = service.getRecentTasks( + 100, 0, userId).getList(); + if(recentTasks != null) { + for(ActivityManager.RecentTaskInfo recentTask : recentTasks) { + int taskId = recentTask.persistentId; + assertEquals("The task id " + taskId + " should not belong to user " + userId, + taskId / UserHandle.PER_USER_RANGE, userId); + } + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/am/ActivityRecordTests.java new file mode 100644 index 000000000000..616cc0f4007f --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityRecordTests.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; +import static com.android.server.am.ActivityStack.ActivityState.PAUSING; +import static com.android.server.am.ActivityStack.ActivityState.STOPPED; +import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; + +import static junit.framework.TestCase.assertNotNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityOptions; +import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.PauseActivityItem; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.MutableBoolean; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; + +/** + * Tests for the {@link ActivityRecord} class. + * + * Build/Install/Run: + * atest WmTests:com.android.server.am.ActivityRecordTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityRecordTests extends ActivityTestsBase { + private ActivityManagerService mService; + private TestActivityStack mStack; + private TaskRecord mTask; + private ActivityRecord mActivity; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mService = createActivityManagerService(); + mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + mActivity = new ActivityBuilder(mService).setTask(mTask).build(); + } + + @Test + public void testStackCleanupOnClearingTask() throws Exception { + mActivity.setTask(null); + assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); + } + + @Test + public void testStackCleanupOnActivityRemoval() throws Exception { + mTask.removeActivity(mActivity); + assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1); + } + + @Test + public void testStackCleanupOnTaskRemoval() throws Exception { + mStack.removeTask(mTask, null /*reason*/, REMOVE_TASK_MODE_MOVING); + // Stack should be gone on task removal. + assertNull(mService.mStackSupervisor.getStack(mStack.mStackId)); + } + + @Test + public void testNoCleanupMovingActivityInSameStack() throws Exception { + final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack) + .build(); + mActivity.reparent(newTask, 0, null /*reason*/); + assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0); + } + + @Test + public void testPausingWhenVisibleFromStopped() throws Exception { + final MutableBoolean pauseFound = new MutableBoolean(false); + doAnswer((InvocationOnMock invocationOnMock) -> { + final ClientTransaction transaction = invocationOnMock.getArgument(0); + if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) { + pauseFound.value = true; + } + return null; + }).when(mActivity.app.thread).scheduleTransaction(any()); + + mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); + + // The activity is in the focused stack so it should not move to paused. + mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); + assertTrue(mActivity.isState(STOPPED)); + assertFalse(pauseFound.value); + + // Clear focused stack + mActivity.mStackSupervisor.mFocusedStack = null; + + // In the unfocused stack, the activity should move to paused. + mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); + assertTrue(mActivity.isState(PAUSING)); + assertTrue(pauseFound.value); + + // Make sure that the state does not change for current non-stopping states. + mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); + + mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); + + assertTrue(mActivity.isState(INITIALIZING)); + + // Make sure the state does not change if we are not the current top activity. + mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind"); + + // Make sure that the state does not change when we have an activity becoming translucent + final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); + mStack.mTranslucentActivityWaiting = topActivity; + mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); + + assertTrue(mActivity.isState(STOPPED)); + } + + @Test + public void testPositionLimitedAspectRatioNavBarBottom() throws Exception { + verifyPositionWithLimitedAspectRatio(NAV_BAR_BOTTOM, new Rect(0, 0, 1000, 2000), 1.5f, + new Rect(0, 0, 1000, 1500)); + } + + @Test + public void testPositionLimitedAspectRatioNavBarLeft() throws Exception { + verifyPositionWithLimitedAspectRatio(NAV_BAR_LEFT, new Rect(0, 0, 2000, 1000), 1.5f, + new Rect(500, 0, 2000, 1000)); + } + + @Test + public void testPositionLimitedAspectRatioNavBarRight() throws Exception { + verifyPositionWithLimitedAspectRatio(NAV_BAR_RIGHT, new Rect(0, 0, 2000, 1000), 1.5f, + new Rect(0, 0, 1500, 1000)); + } + + private void verifyPositionWithLimitedAspectRatio(int navBarPosition, Rect taskBounds, + float aspectRatio, Rect expectedActivityBounds) { + // Verify with nav bar on the right. + when(mService.mWindowManager.getNavBarPosition()).thenReturn(navBarPosition); + mTask.getConfiguration().windowConfiguration.setAppBounds(taskBounds); + mActivity.info.maxAspectRatio = aspectRatio; + mActivity.ensureActivityConfiguration( + 0 /* globalChanges */, false /* preserveWindow */); + assertEquals(expectedActivityBounds, mActivity.getBounds()); + } + + @Test + public void testCanBeLaunchedOnDisplay() throws Exception { + testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/, + true /*activityResizeable*/, true /*expected*/); + + testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/, + false /*activityResizeable*/, false /*expected*/); + + testSupportsLaunchingResizeable(true /*taskPresent*/, false /*taskResizeable*/, + true /*activityResizeable*/, false /*expected*/); + + testSupportsLaunchingResizeable(true /*taskPresent*/, true /*taskResizeable*/, + false /*activityResizeable*/, true /*expected*/); + } + + @Test + public void testsApplyOptionsLocked() { + ActivityOptions activityOptions = ActivityOptions.makeBasic(); + + // Set and apply options for ActivityRecord. Pending options should be cleared + mActivity.updateOptionsLocked(activityOptions); + mActivity.applyOptionsLocked(); + assertNull(mActivity.pendingOptions); + + // Set options for two ActivityRecords in same Task. Apply one ActivityRecord options. + // Pending options should be cleared for both ActivityRecords + ActivityRecord activity2 = new ActivityBuilder(mService).setTask(mTask).build(); + activity2.updateOptionsLocked(activityOptions); + mActivity.updateOptionsLocked(activityOptions); + mActivity.applyOptionsLocked(); + assertNull(mActivity.pendingOptions); + assertNull(activity2.pendingOptions); + + // Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options. + // Pending options should be cleared for only ActivityRecord that was applied + TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + activity2 = new ActivityBuilder(mService).setTask(task2).build(); + activity2.updateOptionsLocked(activityOptions); + mActivity.updateOptionsLocked(activityOptions); + mActivity.applyOptionsLocked(); + assertNull(mActivity.pendingOptions); + assertNotNull(activity2.pendingOptions); + } + + private void testSupportsLaunchingResizeable(boolean taskPresent, boolean taskResizeable, + boolean activityResizeable, boolean expected) { + mService.mSupportsMultiWindow = true; + + final TaskRecord task = taskPresent + ? new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build() : null; + + if (task != null) { + task.setResizeMode(taskResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE); + } + + final ActivityRecord record = new ActivityBuilder(mService).setTask(task).build(); + record.info.resizeMode = activityResizeable + ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; + + record.canBeLaunchedOnDisplay(DEFAULT_DISPLAY); + + + verify(mService.mStackSupervisor, times(1)).canPlaceEntityOnDisplay(anyInt(), eq(expected), + anyInt(), anyInt(), eq(record.info)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/am/ActivityStackSupervisorTests.java new file mode 100644 index 000000000000..2209b66e3ed6 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.ActivityManager.START_DELIVERED_TO_TOP; +import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; +import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; + +import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; +import static com.android.server.am.ActivityStackSupervisor + .MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityOptions; +import android.app.WaitResult; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.SparseIntArray; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; + +import java.util.ArrayList; + +/** + * Tests for the {@link ActivityStackSupervisor} class. + * + * Build/Install/Run: + * atest WmTests:ActivityStackSupervisorTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityStackSupervisorTests extends ActivityTestsBase { + private ActivityManagerService mService; + private ActivityStackSupervisor mSupervisor; + private ActivityStack mFullscreenStack; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mService = createActivityManagerService(); + mSupervisor = mService.mStackSupervisor; + mFullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + } + + /** + * This test ensures that we do not try to restore a task based off an invalid task id. The + * stack supervisor is a test version so there will be no tasks present. We should expect + * {@code null} to be returned in this case. + */ + @Test + public void testRestoringInvalidTask() throws Exception { + TaskRecord task = mSupervisor.anyTaskForIdLocked(0 /*taskId*/, + MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */); + assertNull(task); + } + + /** + * This test ensures that an existing task in the pinned stack is moved to the fullscreen + * activity stack when a new task is added. + */ + @Test + public void testReplacingTaskInPinnedStack() throws Exception { + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + final TaskRecord firstTask = firstActivity.getTask(); + + final ActivityRecord secondActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + final TaskRecord secondTask = secondActivity.getTask(); + + mSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack", mFullscreenStack); + + // Ensure full screen stack has both tasks. + ensureStackPlacement(mFullscreenStack, firstTask, secondTask); + + // Move first activity to pinned stack. + final Rect sourceBounds = new Rect(); + mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds, + 0f /*aspectRatio*/, "initialMove"); + + final ActivityDisplay display = mFullscreenStack.getDisplay(); + ActivityStack pinnedStack = display.getPinnedStack(); + // Ensure a task has moved over. + ensureStackPlacement(pinnedStack, firstTask); + ensureStackPlacement(mFullscreenStack, secondTask); + + // Move second activity to pinned stack. + mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds, + 0f /*aspectRatio*/, "secondMove"); + + // Need to get stacks again as a new instance might have been created. + pinnedStack = display.getPinnedStack(); + mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + // Ensure stacks have swapped tasks. + ensureStackPlacement(pinnedStack, secondTask); + ensureStackPlacement(mFullscreenStack, firstTask); + } + + private static void ensureStackPlacement(ActivityStack stack, TaskRecord... tasks) { + final ArrayList stackTasks = stack.getAllTasks(); + assertEquals(stackTasks.size(), tasks != null ? tasks.length : 0); + + if (tasks == null) { + return; + } + + for (TaskRecord task : tasks) { + assertTrue(stackTasks.contains(task)); + } + } + + /** + * Ensures that an activity is removed from the stopping activities list once it is resumed. + */ + @Test + public void testStoppingActivityRemovedWhenResumed() throws Exception { + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + mSupervisor.mStoppingActivities.add(firstActivity); + + firstActivity.completeResumeLocked(); + + assertFalse(mSupervisor.mStoppingActivities.contains(firstActivity)); + } + + /** + * Ensures that waiting results are notified of launches. + */ + @Test + public void testReportWaitingActivityLaunchedIfNeeded() throws Exception { + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + + // #notifyAll will be called on the ActivityManagerService. we must hold the object lock + // when this happens. + synchronized (mSupervisor.mService) { + final WaitResult taskToFrontWait = new WaitResult(); + mSupervisor.mWaitingActivityLaunched.add(taskToFrontWait); + mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, START_TASK_TO_FRONT); + + assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty()); + assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT); + assertEquals(taskToFrontWait.who, null); + + final WaitResult deliverToTopWait = new WaitResult(); + mSupervisor.mWaitingActivityLaunched.add(deliverToTopWait); + mSupervisor.reportWaitingActivityLaunchedIfNeeded(firstActivity, + START_DELIVERED_TO_TOP); + + assertTrue(mSupervisor.mWaitingActivityLaunched.isEmpty()); + assertEquals(deliverToTopWait.result, START_DELIVERED_TO_TOP); + assertEquals(deliverToTopWait.who, firstActivity.realActivity); + } + } + + @Test + public void testApplySleepTokensLocked() throws Exception { + final ActivityDisplay display = mSupervisor.getDefaultDisplay(); + final KeyguardController keyguard = mSupervisor.getKeyguardController(); + final ActivityStack stack = mock(ActivityStack.class); + display.addChild(stack, 0 /* position */); + + // Make sure we wake and resume in the case the display is turning on and the keyguard is + // not showing. + verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, + false /* displayShouldSleep */, true /* isFocusedStack */, + false /* keyguardShowing */, true /* expectWakeFromSleep */, + true /* expectResumeTopActivity */); + + // Make sure we wake and don't resume when the display is turning on and the keyguard is + // showing. + verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, + false /* displayShouldSleep */, true /* isFocusedStack */, + true /* keyguardShowing */, true /* expectWakeFromSleep */, + false /* expectResumeTopActivity */); + + // Make sure we wake and don't resume when the display is turning on and the keyguard is + // not showing as unfocused. + verifySleepTokenBehavior(display, keyguard, stack, true /*displaySleeping*/, + false /* displayShouldSleep */, false /* isFocusedStack */, + false /* keyguardShowing */, true /* expectWakeFromSleep */, + false /* expectResumeTopActivity */); + + // Should not do anything if the display state hasn't changed. + verifySleepTokenBehavior(display, keyguard, stack, false /*displaySleeping*/, + false /* displayShouldSleep */, true /* isFocusedStack */, + false /* keyguardShowing */, false /* expectWakeFromSleep */, + false /* expectResumeTopActivity */); + } + + private void verifySleepTokenBehavior(ActivityDisplay display, KeyguardController keyguard, + ActivityStack stack, boolean displaySleeping, boolean displayShouldSleep, + boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep, + boolean expectResumeTopActivity) { + reset(stack); + + doReturn(displayShouldSleep).when(display).shouldSleep(); + doReturn(displaySleeping).when(display).isSleeping(); + doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt()); + + mSupervisor.mFocusedStack = isFocusedStack ? stack : null; + mSupervisor.applySleepTokensLocked(true); + verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked(); + verify(stack, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked( + null /* target */, null /* targetOptions */); + } + + @Test + public void testTopRunningActivityLockedWithNonExistentDisplay() throws Exception { + // Create display that ActivityManagerService does not know about + final int unknownDisplayId = 100; + + doAnswer((InvocationOnMock invocationOnMock) -> { + final SparseIntArray displayIds = invocationOnMock.getArgument(0); + displayIds.put(0, unknownDisplayId); + return null; + }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); + + mSupervisor.mFocusedStack = mock(ActivityStack.class); + + // Supervisor should skip over the non-existent display. + assertEquals(null, mSupervisor.topRunningActivityLocked()); + } + + /** + * Verifies that removal of activity with task and stack is done correctly. + */ + @Test + public void testRemovingStackOnAppCrash() throws Exception { + final ActivityDisplay defaultDisplay = mService.mStackSupervisor.getDefaultDisplay(); + final int originalStackCount = defaultDisplay.getChildCount(); + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(stack).build(); + + assertEquals(originalStackCount + 1, defaultDisplay.getChildCount()); + + // Let's pretend that the app has crashed. + firstActivity.app.thread = null; + mService.mStackSupervisor.finishTopCrashedActivitiesLocked(firstActivity.app, "test"); + + // Verify that the stack was removed. + assertEquals(originalStackCount, defaultDisplay.getChildCount()); + } + + @Test + public void testFocusability() throws Exception { + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(stack).build(); + + // Under split screen primary we should be focusable when not minimized + mService.mStackSupervisor.setDockedStackMinimized(false); + assertTrue(stack.isFocusable()); + assertTrue(activity.isFocusable()); + + // Under split screen primary we should not be focusable when minimized + mService.mStackSupervisor.setDockedStackMinimized(true); + assertFalse(stack.isFocusable()); + assertFalse(activity.isFocusable()); + + final ActivityStack pinnedStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(pinnedStack).build(); + + // We should not be focusable when in pinned mode + assertFalse(pinnedStack.isFocusable()); + assertFalse(pinnedActivity.isFocusable()); + + // Add flag forcing focusability. + pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE; + + // We should not be focusable when in pinned mode + assertTrue(pinnedStack.isFocusable()); + assertTrue(pinnedActivity.isFocusable()); + + // Without the overridding activity, stack should not be focusable. + pinnedStack.removeTask(pinnedActivity.getTask(), "testFocusability", + REMOVE_TASK_MODE_DESTROYING); + assertFalse(pinnedStack.isFocusable()); + } + + /** + * Verifies the correct activity is returned when querying the top running activity. + */ + @Test + public void testTopRunningActivity() throws Exception { + // Create stack to hold focus + final ActivityStack emptyStack = mService.mStackSupervisor.getDefaultDisplay() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + final KeyguardController keyguard = mSupervisor.getKeyguardController(); + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(stack).build(); + + mSupervisor.mFocusedStack = emptyStack; + + doAnswer((InvocationOnMock invocationOnMock) -> { + final SparseIntArray displayIds = invocationOnMock.getArgument(0); + displayIds.put(0, mSupervisor.getDefaultDisplay().mDisplayId); + return null; + }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); + + // Make sure the top running activity is not affected when keyguard is not locked + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + + // Check to make sure activity not reported when it cannot show on lock and lock is on. + doReturn(true).when(keyguard).isKeyguardLocked(); + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + + // Change focus to stack with activity. + mSupervisor.mFocusedStack = stack; + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + + // Add activity that should be shown on the keyguard. + final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(stack) + .setActivityFlags(FLAG_SHOW_WHEN_LOCKED) + .build(); + + // Ensure the show when locked activity is returned. + assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + + // Change focus back to empty stack + mSupervisor.mFocusedStack = emptyStack; + // Ensure the show when locked activity is returned when not the focused stack + assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + } + + /** + * Verify that split-screen primary stack will be chosen if activity is launched that targets + * split-screen secondary, but a matching existing instance is found on top of split-screen + * primary stack. + */ + @Test + public void testSplitScreenPrimaryChosenWhenTopActivityLaunchedToSecondary() throws Exception { + // Create primary split-screen stack with a task and an activity. + final ActivityStack primaryStack = mService.mStackSupervisor.getDefaultDisplay() + .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(primaryStack).build(); + final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build(); + + // Find a launch stack for the top activity in split-screen primary, while requesting + // split-screen secondary. + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); + final ActivityStack result = mSupervisor.getLaunchStack(r, options, task, true /* onTop */); + + // Assert that the primary stack is returned. + assertEquals(primaryStack, result); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/am/ActivityStackTests.java new file mode 100644 index 000000000000..cac264b81198 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityStackTests.java @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + +import static com.android.server.am.ActivityStack.ActivityState.PAUSING; +import static com.android.server.am.ActivityStack.ActivityState.RESUMED; +import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.pm.ActivityInfo; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link ActivityStack} class. + * + * Build/Install/Run: + * atest WmTests:ActivityStackTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityStackTests extends ActivityTestsBase { + private ActivityManagerService mService; + private ActivityStackSupervisor mSupervisor; + private ActivityDisplay mDefaultDisplay; + private ActivityStack mStack; + private TaskRecord mTask; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mService = createActivityManagerService(); + mSupervisor = mService.mStackSupervisor; + mDefaultDisplay = mService.mStackSupervisor.getDefaultDisplay(); + mStack = mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); + } + + @Test + public void testEmptyTaskCleanupOnRemove() throws Exception { + assertNotNull(mTask.getWindowContainerController()); + mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); + assertNull(mTask.getWindowContainerController()); + } + + @Test + public void testOccupiedTaskCleanupOnRemove() throws Exception { + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); + assertNotNull(mTask.getWindowContainerController()); + mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING); + assertNotNull(mTask.getWindowContainerController()); + } + + @Test + public void testResumedActivity() throws Exception { + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); + assertEquals(mStack.getResumedActivity(), null); + r.setState(RESUMED, "testResumedActivity"); + assertEquals(mStack.getResumedActivity(), r); + r.setState(PAUSING, "testResumedActivity"); + assertEquals(mStack.getResumedActivity(), null); + } + + @Test + public void testResumedActivityFromTaskReparenting() { + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); + // Ensure moving task between two stacks updates resumed activity + r.setState(RESUMED, "testResumedActivityFromTaskReparenting"); + assertEquals(mStack.getResumedActivity(), r); + + final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + mTask.reparent(destStack, true /* toTop */, TaskRecord.REPARENT_KEEP_STACK_AT_FRONT, + false /* animate */, true /* deferResume*/, + "testResumedActivityFromTaskReparenting"); + + assertEquals(mStack.getResumedActivity(), null); + assertEquals(destStack.getResumedActivity(), r); + } + + @Test + public void testResumedActivityFromActivityReparenting() { + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); + // Ensure moving task between two stacks updates resumed activity + r.setState(RESUMED, "testResumedActivityFromActivityReparenting"); + assertEquals(mStack.getResumedActivity(), r); + + final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord destTask = new TaskBuilder(mSupervisor).setStack(destStack).build(); + + mTask.removeActivity(r); + destTask.addActivityToTop(r); + + assertEquals(mStack.getResumedActivity(), null); + assertEquals(destStack.getResumedActivity(), r); + } + + @Test + public void testPrimarySplitScreenToFullscreenWhenMovedToBack() throws Exception { + // Create primary splitscreen stack. This will create secondary stacks and places the + // existing fullscreen stack on the bottom. + final ActivityStack primarySplitScreen = mDefaultDisplay.createStack( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + // Assert windowing mode. + assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + + // Move primary to back. + primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", + null /* task */); + + // Assert that stack is at the bottom. + assertEquals(mDefaultDisplay.getIndexOf(primarySplitScreen), 0); + + // Ensure no longer in splitscreen. + assertEquals(primarySplitScreen.getWindowingMode(), WINDOWING_MODE_FULLSCREEN); + } + + @Test + public void testStopActivityWhenActivityDestroyed() throws Exception { + final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); + r.info.flags |= ActivityInfo.FLAG_NO_HISTORY; + mSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", mStack); + mStack.stopActivityLocked(r); + // Mostly testing to make sure there is a crash in the call part, so if we get here we are + // good-to-go! + } + + @Test + public void testFindTaskWithOverlay() throws Exception { + final ActivityRecord r = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(mStack) + .setUid(0) + .build(); + final TaskRecord task = r.getTask(); + // Overlay must be for a different user to prevent recognizing a matching top activity + final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task) + .setUid(UserHandle.PER_USER_RANGE * 2).build(); + taskOverlay.mTaskOverlay = true; + + final ActivityStackSupervisor.FindTaskResult result = + new ActivityStackSupervisor.FindTaskResult(); + mStack.findTaskLocked(r, result); + + assertEquals(task.getTopActivity(false /* includeOverlays */), r); + assertEquals(task.getTopActivity(true /* includeOverlays */), taskOverlay); + assertNotNull(result.r); + } + + @Test + public void testShouldBeVisible_Fullscreen() throws Exception { + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + + final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack + // should be visible since it is always on-top. + fullscreenStack.setIsTranslucent(false); + assertFalse(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + assertTrue(fullscreenStack.shouldBeVisible(null /* starting */)); + + // Home stack should be visible behind a translucent fullscreen stack. + fullscreenStack.setIsTranslucent(true); + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(pinnedStack.shouldBeVisible(null /* starting */)); + } + + @Test + public void testShouldBeVisible_SplitScreen() throws Exception { + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + // Home stack should always be fullscreen for this test. + homeStack.setSupportsSplitScreen(false); + final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + // Home stack shouldn't be visible if both halves of split-screen are opaque. + splitScreenPrimary.setIsTranslucent(false); + splitScreenSecondary.setIsTranslucent(false); + assertFalse(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + + // Home stack should be visible if one of the halves of split-screen is translucent. + splitScreenPrimary.setIsTranslucent(true); + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + + final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + // First split-screen secondary shouldn't be visible behind another opaque split-split + // secondary. + splitScreenSecondary2.setIsTranslucent(false); + assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + // First split-screen secondary should be visible behind another translucent split-screen + // secondary. + splitScreenSecondary2.setIsTranslucent(true); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); + + // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack. + assistantStack.setIsTranslucent(false); + assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + // Split-screen stacks should be visible behind a translucent fullscreen stack. + assistantStack.setIsTranslucent(true); + assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + + // Assistant stack shouldn't be visible behind translucent split-screen stack + assistantStack.setIsTranslucent(false); + splitScreenPrimary.setIsTranslucent(true); + splitScreenSecondary2.setIsTranslucent(true); + splitScreenSecondary2.moveToFront("testShouldBeVisible_SplitScreen"); + splitScreenPrimary.moveToFront("testShouldBeVisible_SplitScreen"); + assertFalse(assistantStack.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */)); + } + + @Test + public void testShouldBeVisible_Finishing() throws Exception { + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack translucentStack = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + translucentStack.setIsTranslucent(true); + + assertTrue(homeStack.shouldBeVisible(null /* starting */)); + assertTrue(translucentStack.shouldBeVisible(null /* starting */)); + + final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked(); + topRunningHomeActivity.finishing = true; + final ActivityRecord topRunningTranslucentActivity = + translucentStack.topRunningActivityLocked(); + topRunningTranslucentActivity.finishing = true; + + // Home shouldn't be visible since its activity is marked as finishing and it isn't the top + // of the stack list. + assertFalse(homeStack.shouldBeVisible(null /* starting */)); + // Home should be visible if we are starting an activity within it. + assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */)); + // The translucent stack should be visible since it is the top of the stack list even though + // it has its activity marked as finishing. + assertTrue(translucentStack.shouldBeVisible(null /* starting */)); + } + + @Test + public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindFullscreen() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + homeStack.setIsTranslucent(false); + fullscreenStack.setIsTranslucent(false); + + // Ensure that we don't move the home stack if it is already behind the top fullscreen stack + int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); + assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); + } + + @Test + public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeBehindTranslucent() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + homeStack.setIsTranslucent(false); + fullscreenStack.setIsTranslucent(true); + + // Ensure that we don't move the home stack if it is already behind the top fullscreen stack + int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); + assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); + } + + @Test + public void testMoveHomeStackBehindBottomMostVisibleStack_NoMoveHomeOnTop() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + + homeStack.setIsTranslucent(false); + fullscreenStack.setIsTranslucent(false); + + // Ensure we don't move the home stack if it is already on top + int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == null); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); + assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); + } + + @Test + public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreen() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + homeStack.setIsTranslucent(false); + fullscreenStack1.setIsTranslucent(false); + fullscreenStack2.setIsTranslucent(false); + + // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the + // pinned stack + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); + } + + @Test + public void testMoveHomeStackBehindBottomMostVisibleStack_MoveHomeBehindFullscreenAndTranslucent() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + + homeStack.setIsTranslucent(false); + fullscreenStack1.setIsTranslucent(false); + fullscreenStack2.setIsTranslucent(true); + + // Ensure that we move the home stack behind the bottom most non-translucent fullscreen + // stack + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); + } + + @Test + public void testMoveHomeStackBehindStack_BehindHomeStack() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + + homeStack.setIsTranslucent(false); + fullscreenStack1.setIsTranslucent(false); + fullscreenStack2.setIsTranslucent(false); + + // Ensure we don't move the home stack behind itself + int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack); + mDefaultDisplay.moveStackBehindStack(homeStack, homeStack); + assertTrue(mDefaultDisplay.getIndexOf(homeStack) == homeStackIndex); + } + + @Test + public void testMoveHomeStackBehindStack() { + mDefaultDisplay.removeChild(mStack); + + final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack fullscreenStack2 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack fullscreenStack3 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack fullscreenStack4 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + + mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack1); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack1); + mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); + mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack4); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack4); + mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2); + assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); + } + + @Test + public void testSplitScreenMoveToFront() throws Exception { + final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(mDefaultDisplay, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); + + splitScreenPrimary.setIsTranslucent(false); + splitScreenSecondary.setIsTranslucent(false); + assistantStack.setIsTranslucent(false); + + assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertTrue(assistantStack.shouldBeVisible(null /* starting */)); + + splitScreenSecondary.moveToFront("testSplitScreenMoveToFront"); + + assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); + assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); + assertFalse(assistantStack.shouldBeVisible(null /* starting */)); + } + + private T createStackForShouldBeVisibleTest( + ActivityDisplay display, int windowingMode, int activityType, boolean onTop) { + final T stack = display.createStack(windowingMode, activityType, onTop); + final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack) + .setCreateTask(true).build(); + return stack; + } + + @Test + public void testFinishDisabledPackageActivities() throws Exception { + final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build(); + + // Making the second activity a task overlay without an app means it will be removed from + // the task's activities as well once first activity is removed. + secondActivity.mTaskOverlay = true; + secondActivity.app = null; + + assertEquals(mTask.mActivities.size(), 2); + + mStack.finishDisabledPackageActivitiesLocked(firstActivity.packageName, null, + true /* doit */, true /* evenPersistent */, UserHandle.USER_ALL); + + assertTrue(mTask.mActivities.isEmpty()); + assertTrue(mStack.getAllTasks().isEmpty()); + } + + @Test + public void testHandleAppDied() throws Exception { + final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build(); + final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build(); + + // Making the first activity a task overlay means it will be removed from the task's + // activities as well once second activity is removed as handleAppDied processes the + // activity list in reverse. + firstActivity.mTaskOverlay = true; + firstActivity.app = null; + + // second activity will be immediately removed as it has no state. + secondActivity.haveState = false; + + assertEquals(mTask.mActivities.size(), 2); + + mStack.handleAppDiedLocked(secondActivity.app); + + assertTrue(mTask.mActivities.isEmpty()); + assertTrue(mStack.getAllTasks().isEmpty()); + } + + @Test + public void testShouldSleepActivities() throws Exception { + // When focused activity and keyguard is going away, we should not sleep regardless + // of the display state + verifyShouldSleepActivities(true /* focusedStack */, true /*keyguardGoingAway*/, + true /* displaySleeping */, false /* expected*/); + + // When not the focused stack, defer to display sleeping state. + verifyShouldSleepActivities(false /* focusedStack */, true /*keyguardGoingAway*/, + true /* displaySleeping */, true /* expected*/); + + // If keyguard is going away, defer to the display sleeping state. + verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/, + true /* displaySleeping */, true /* expected*/); + verifyShouldSleepActivities(true /* focusedStack */, false /*keyguardGoingAway*/, + false /* displaySleeping */, false /* expected*/); + } + + @Test + public void testStackOrderChangedOnRemoveStack() throws Exception { + StackOrderChangedListener listener = new StackOrderChangedListener(); + mDefaultDisplay.registerStackOrderChangedListener(listener); + try { + mDefaultDisplay.removeChild(mStack); + } finally { + mDefaultDisplay.unregisterStackOrderChangedListener(listener); + } + assertTrue(listener.changed); + } + + @Test + public void testStackOrderChangedOnAddPositionStack() throws Exception { + mDefaultDisplay.removeChild(mStack); + + StackOrderChangedListener listener = new StackOrderChangedListener(); + mDefaultDisplay.registerStackOrderChangedListener(listener); + try { + mDefaultDisplay.addChild(mStack, 0); + } finally { + mDefaultDisplay.unregisterStackOrderChangedListener(listener); + } + assertTrue(listener.changed); + } + + @Test + public void testStackOrderChangedOnPositionStack() throws Exception { + StackOrderChangedListener listener = new StackOrderChangedListener(); + try { + final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + mDefaultDisplay.registerStackOrderChangedListener(listener); + mDefaultDisplay.positionChildAtBottom(fullscreenStack1); + } finally { + mDefaultDisplay.unregisterStackOrderChangedListener(listener); + } + assertTrue(listener.changed); + } + + private void verifyShouldSleepActivities(boolean focusedStack, + boolean keyguardGoingAway, boolean displaySleeping, boolean expected) { + mSupervisor.mFocusedStack = focusedStack ? mStack : null; + + final ActivityDisplay display = mock(ActivityDisplay.class); + final KeyguardController keyguardController = mSupervisor.getKeyguardController(); + + doReturn(display).when(mSupervisor).getActivityDisplay(anyInt()); + doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway(); + doReturn(displaySleeping).when(display).isSleeping(); + + assertEquals(expected, mStack.shouldSleepActivities()); + } + + private class StackOrderChangedListener implements ActivityDisplay.OnStackOrderChangedListener { + boolean changed = false; + + @Override + public void onStackOrderChanged() { + changed = true; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/am/ActivityStartControllerTests.java new file mode 100644 index 000000000000..37a5a17caf47 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityStartControllerTests.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Intent; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; +import com.android.server.am.ActivityStarter.Factory; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +/** + * Tests for the {@link ActivityStartController} class. + * + * Build/Install/Run: + * atest WmTests:ActivityStartControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityStartControllerTests extends ActivityTestsBase { + private ActivityManagerService mService; + private ActivityStartController mController; + private Factory mFactory; + private ActivityStarter mStarter; + + @Override + public void setUp() throws Exception { + super.setUp(); + mService = createActivityManagerService(); + mFactory = mock(Factory.class); + mController = new ActivityStartController(mService, mService.mStackSupervisor, mFactory); + mStarter = spy(new ActivityStarter(mController, mService, mService.mStackSupervisor, + mock(ActivityStartInterceptor.class))); + doReturn(mStarter).when(mFactory).obtain(); + } + + /** + * Ensures that pending launches are processed. + */ + @Test + public void testPendingActivityLaunches() { + final Random random = new Random(); + + final ActivityRecord activity = new ActivityBuilder(mService).build(); + final ActivityRecord source = new ActivityBuilder(mService).build(); + final int startFlags = random.nextInt(); + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ProcessRecord process= new ProcessRecord(null, null, + mService.mContext.getApplicationInfo(), "name", 12345); + + mController.addPendingActivityLaunch( + new PendingActivityLaunch(activity, source, startFlags, stack, process)); + final boolean resume = random.nextBoolean(); + mController.doPendingActivityLaunches(resume); + + verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null), + eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null)); + } + + + /** + * Ensures instances are recycled after execution. + */ + @Test + public void testRecycling() throws Exception { + final Intent intent = new Intent(); + final ActivityStarter optionStarter = new ActivityStarter(mController, mService, + mService.mStackSupervisor, mock(ActivityStartInterceptor.class)); + optionStarter + .setIntent(intent) + .setReason("Test") + .execute(); + verify(mFactory, times(1)).recycle(eq(optionStarter)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/am/ActivityStartInterceptorTest.java new file mode 100644 index 000000000000..8f2d2844fc9e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityStartInterceptorTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.when; + +import android.app.KeyguardManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.support.test.filters.SmallTest; +import android.testing.DexmakerShareClassLoaderRule; + +import com.android.internal.app.SuspendedAppActivity; +import com.android.internal.app.UnlaunchableAppActivity; +import com.android.server.LocalServices; +import com.android.server.pm.PackageManagerService; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ActivityStartInterceptorTest}. + * + * Build/Install/Run: + * bit WmTests:ActivityStartInterceptorTest + */ +@SmallTest +public class ActivityStartInterceptorTest { + private static final int TEST_USER_ID = 1; + private static final int TEST_REAL_CALLING_UID = 2; + private static final int TEST_REAL_CALLING_PID = 3; + private static final String TEST_CALLING_PACKAGE = "com.test.caller"; + private static final int TEST_START_FLAGS = 4; + private static final Intent ADMIN_SUPPORT_INTENT = + new Intent("com.test.ADMIN_SUPPORT"); + private static final Intent CONFIRM_CREDENTIALS_INTENT = + new Intent("com.test.CONFIRM_CREDENTIALS"); + private static final UserInfo PARENT_USER_INFO = new UserInfo(0 /* userId */, "parent", + 0 /* flags */); + private static final String TEST_PACKAGE_NAME = "com.test.package"; + + @Rule + public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = + new DexmakerShareClassLoaderRule(); + + @Mock + private Context mContext; + @Mock + private ActivityManagerService mService; + @Mock + private ActivityStackSupervisor mSupervisor; + @Mock + private DevicePolicyManagerInternal mDevicePolicyManager; + @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock + private UserManager mUserManager; + @Mock + private UserController mUserController; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private PackageManagerService mPackageManager; + + private ActivityStartInterceptor mInterceptor; + private ActivityInfo mAInfo = new ActivityInfo(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext, + mUserController); + mInterceptor.setStates(TEST_USER_ID, TEST_REAL_CALLING_PID, TEST_REAL_CALLING_UID, + TEST_START_FLAGS, TEST_CALLING_PACKAGE); + + // Mock DevicePolicyManagerInternal + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.addService(DevicePolicyManagerInternal.class, + mDevicePolicyManager); + when(mDevicePolicyManager + .createShowAdminSupportIntent(TEST_USER_ID, true)) + .thenReturn(ADMIN_SUPPORT_INTENT); + when(mService.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal); + + // Mock UserManager + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mUserManager.getProfileParent(TEST_USER_ID)).thenReturn(PARENT_USER_INFO); + + // Mock KeyguardManager + when(mContext.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(mKeyguardManager); + when(mKeyguardManager.createConfirmDeviceCredentialIntent( + nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID))). + thenReturn(CONFIRM_CREDENTIALS_INTENT); + + // Mock PackageManager + when(mService.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(null); + + // Initialise activity info + mAInfo.applicationInfo = new ApplicationInfo(); + mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; + } + + @Test + public void testSuspendedByAdminPackage() { + // GIVEN the package we're about to launch is currently suspended + mAInfo.applicationInfo.flags = FLAG_SUSPENDED; + + when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(PLATFORM_PACKAGE_NAME); + + // THEN calling intercept returns true + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + + // THEN the returned intent is the admin support intent + assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent); + } + + @Test + public void testSuspendedPackage() { + mAInfo.applicationInfo.flags = FLAG_SUSPENDED; + final String suspendingPackage = "com.test.suspending.package"; + final String dialogMessage = "Test Message"; + when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(suspendingPackage); + when(mPackageManagerInternal.getSuspendedDialogMessage(TEST_PACKAGE_NAME, TEST_USER_ID)) + .thenReturn(dialogMessage); + // THEN calling intercept returns true + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + + // Check intent parameters + assertEquals(dialogMessage, + mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_DIALOG_MESSAGE)); + assertEquals(suspendingPackage, + mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDING_PACKAGE)); + assertEquals(TEST_PACKAGE_NAME, + mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDED_PACKAGE)); + assertEquals(TEST_USER_ID, mInterceptor.mIntent.getIntExtra(Intent.EXTRA_USER_ID, -1000)); + } + + @Test + public void testInterceptQuietProfile() { + // GIVEN that the user the activity is starting as is currently in quiet mode + when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true); + + // THEN calling intercept returns true + assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + + // THEN the returned intent is the quiet mode intent + assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID) + .filterEquals(mInterceptor.mIntent)); + } + + @Test + public void testWorkChallenge() { + // GIVEN that the user the activity is starting as is currently locked + when(mUserController.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true); + + // THEN calling intercept returns true + mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null); + + // THEN the returned intent is the quiet mode intent + assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent)); + } + + @Test + public void testNoInterception() { + // GIVEN that none of the interception conditions are met + + // THEN calling intercept returns false + assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/am/ActivityStarterTests.java new file mode 100644 index 000000000000..048df43916e0 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityStarterTests.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.ActivityManager.START_ABORTED; +import static android.app.ActivityManager.START_CLASS_NOT_FOUND; +import static android.app.ActivityManager.START_DELIVERED_TO_TOP; +import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; +import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED; +import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE; +import static android.app.ActivityManager.START_PERMISSION_DENIED; +import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; +import static android.app.ActivityManager.START_SUCCESS; +import static android.app.ActivityManager.START_SWITCHES_CANCELED; +import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; + +import static com.android.server.am.ActivityManagerService.ANIMATE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityOptions; +import android.app.IApplicationThread; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.WindowLayout; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.service.voice.IVoiceInteractionSession; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Gravity; + +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.am.LaunchParamsController.LaunchParamsModifier; +import com.android.server.am.TaskRecord.TaskRecordFactory; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link ActivityStarter} class. + * + * Build/Install/Run: + * atest WmTests:ActivityStarterTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ActivityStarterTests extends ActivityTestsBase { + private ActivityManagerService mService; + private ActivityStarter mStarter; + private ActivityStartController mController; + + private static final int PRECONDITION_NO_CALLER_APP = 1; + private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1; + private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2; + private static final int PRECONDITION_SOURCE_PRESENT = 1 << 3; + private static final int PRECONDITION_REQUEST_CODE = 1 << 4; + private static final int PRECONDITION_SOURCE_VOICE_SESSION = 1 << 5; + private static final int PRECONDITION_NO_VOICE_SESSION_SUPPORT = 1 << 6; + private static final int PRECONDITION_DIFFERENT_UID = 1 << 7; + private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8; + private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9; + private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10; + + @Override + public void setUp() throws Exception { + super.setUp(); + mService = createActivityManagerService(); + mController = mock(ActivityStartController.class); + mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor, + mock(ActivityStartInterceptor.class)); + } + + @Test + public void testUpdateLaunchBounds() throws Exception { + // When in a non-resizeable stack, the task bounds should be updated. + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) + .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) + .build(); + final Rect bounds = new Rect(10, 10, 100, 100); + + mStarter.updateBounds(task, bounds); + assertEquals(task.getOverrideBounds(), bounds); + assertEquals(new Rect(), task.getStack().getOverrideBounds()); + + // When in a resizeable stack, the stack bounds should be updated as well. + final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor) + .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */)) + .build(); + assertTrue(task2.getStack() instanceof PinnedActivityStack); + mStarter.updateBounds(task2, bounds); + + verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId), + eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt()); + + // In the case of no animation, the stack and task bounds should be set immediately. + if (!ANIMATE) { + assertEquals(task2.getStack().getOverrideBounds(), bounds); + assertEquals(task2.getOverrideBounds(), bounds); + } else { + assertEquals(task2.getOverrideBounds(), new Rect()); + } + } + + @Test + public void testStartActivityPreconditions() throws Exception { + verifyStartActivityPreconditions(PRECONDITION_NO_CALLER_APP, START_PERMISSION_DENIED); + verifyStartActivityPreconditions(PRECONDITION_NO_INTENT_COMPONENT, + START_INTENT_NOT_RESOLVED); + verifyStartActivityPreconditions(PRECONDITION_NO_ACTIVITY_INFO, START_CLASS_NOT_FOUND); + verifyStartActivityPreconditions(PRECONDITION_SOURCE_PRESENT | PRECONDITION_REQUEST_CODE, + Intent.FLAG_ACTIVITY_FORWARD_RESULT, START_FORWARD_AND_REQUEST_CONFLICT); + verifyStartActivityPreconditions( + PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT + | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID, + START_NOT_VOICE_COMPATIBLE); + verifyStartActivityPreconditions( + PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT + | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID + | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION, + START_NOT_VOICE_COMPATIBLE); + verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED); + verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING, + START_SWITCHES_CANCELED); + } + + private static boolean containsConditions(int preconditions, int mask) { + return (preconditions & mask) == mask; + } + + private void verifyStartActivityPreconditions(int preconditions, int expectedResult) { + verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult); + } + + /** + * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller + * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP}) + * and the launch flags specified in the intent. The method constructs a call to + * {@link ActivityStarter#execute} based on these preconditions and ensures the result matches + * the expected. It is important to note that the method also checks side effects of the start, + * such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios. + * @param preconditions A bitmask representing the preconditions for the launch + * @param launchFlags The launch flags to be provided by the launch {@link Intent}. + * @param expectedResult The expected result from the launch. + */ + private void verifyStartActivityPreconditions(int preconditions, int launchFlags, + int expectedResult) { + final ActivityManagerService service = createActivityManagerService(); + final IPackageManager packageManager = mock(IPackageManager.class); + final ActivityStartController controller = mock(ActivityStartController.class); + + final ActivityStarter starter = new ActivityStarter(controller, service, + service.mStackSupervisor, mock(ActivityStartInterceptor.class)); + final IApplicationThread caller = mock(IApplicationThread.class); + + // If no caller app, return {@code null} {@link ProcessRecord}. + final ProcessRecord record = containsConditions(preconditions, PRECONDITION_NO_CALLER_APP) + ? null : new ProcessRecord(null, mock(BatteryStatsImpl.class), + mock(ApplicationInfo.class), null, 0); + + doReturn(record).when(service).getRecordForAppLocked(anyObject()); + + final Intent intent = new Intent(); + intent.setFlags(launchFlags); + + final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO) + ? null : new ActivityInfo(); + + IVoiceInteractionSession voiceSession = + containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION) + ? mock(IVoiceInteractionSession.class) : null; + + // Create source token + final ActivityBuilder builder = new ActivityBuilder(service).setTask( + new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build()); + + if (aInfo != null) { + aInfo.applicationInfo = new ApplicationInfo(); + aInfo.applicationInfo.packageName = + ActivityBuilder.getDefaultComponent().getPackageName(); + } + + // Offset uid by one from {@link ActivityInfo} to simulate different uids. + if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) { + builder.setUid(aInfo.applicationInfo.uid + 1); + } + + final ActivityRecord source = builder.build(); + + if (!containsConditions(preconditions, PRECONDITION_NO_INTENT_COMPONENT)) { + intent.setComponent(source.realActivity); + } + + if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) { + doReturn(false).when(service).checkAppSwitchAllowedLocked(anyInt(), anyInt(), anyInt(), + anyInt(), any()); + } + + if (containsConditions(preconditions,PRECONDITION_CANNOT_START_ANY_ACTIVITY)) { + doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission( + any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), + anyBoolean(), anyBoolean(), any(), any(), any()); + } + + try { + if (containsConditions(preconditions, + PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION)) { + doAnswer((inv) -> { + throw new RemoteException(); + }).when(packageManager).activitySupportsIntent(eq(source.realActivity), eq(intent), + any()); + } else { + doReturn(!containsConditions(preconditions, PRECONDITION_NO_VOICE_SESSION_SUPPORT)) + .when(packageManager).activitySupportsIntent(eq(source.realActivity), + eq(intent), any()); + } + } catch (RemoteException e) { + } + + final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT) + || containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION) + ? source.appToken : null; + + final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE) + ? 1 : 0; + + final int result = starter.setCaller(caller) + .setIntent(intent) + .setActivityInfo(aInfo) + .setResultTo(resultTo) + .setRequestCode(requestCode) + .setReason("testLaunchActivityPermissionDenied") + .execute(); + + // In some cases the expected result internally is different than the published result. We + // must use ActivityStarter#getExternalResult to translate. + assertEquals(ActivityStarter.getExternalResult(expectedResult), result); + + // Ensure that {@link ActivityOptions} are aborted with unsuccessful result. + if (expectedResult != START_SUCCESS) { + final ActivityStarter optionStarter = new ActivityStarter(mController, mService, + mService.mStackSupervisor, mock(ActivityStartInterceptor.class)); + final ActivityOptions options = spy(ActivityOptions.makeBasic()); + + final int optionResult = optionStarter.setCaller(caller) + .setIntent(intent) + .setActivityInfo(aInfo) + .setResultTo(resultTo) + .setRequestCode(requestCode) + .setReason("testLaunchActivityPermissionDenied") + .setActivityOptions(new SafeActivityOptions(options)) + .execute(); + verify(options, times(1)).abort(); + } + } + + private ActivityStarter prepareStarter(int launchFlags) { + // always allow test to start activity. + doReturn(true).when(mService.mStackSupervisor).checkStartAnyActivityPermission( + any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), + anyBoolean(), anyBoolean(), any(), any(), any()); + + // instrument the stack and task used. + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) + .setCreateStack(false) + .build(); + + // supervisor needs a focused stack. + mService.mStackSupervisor.mFocusedStack = stack; + + // use factory that only returns spy task. + final TaskRecordFactory factory = mock(TaskRecordFactory.class); + TaskRecord.setTaskRecordFactory(factory); + + // return task when created. + doReturn(task).when(factory).create(any(), anyInt(), any(), any(), any(), any()); + + // direct starter to use spy stack. + doReturn(stack).when(mService.mStackSupervisor) + .getLaunchStack(any(), any(), any(), anyBoolean()); + doReturn(stack).when(mService.mStackSupervisor) + .getLaunchStack(any(), any(), any(), anyBoolean(), anyInt()); + + final Intent intent = new Intent(); + intent.addFlags(launchFlags); + intent.setComponent(ActivityBuilder.getDefaultComponent()); + + final ActivityInfo info = new ActivityInfo(); + + info.applicationInfo = new ApplicationInfo(); + info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName(); + + return new ActivityStarter(mController, mService, + mService.mStackSupervisor, mock(ActivityStartInterceptor.class)) + .setIntent(intent) + .setActivityInfo(info); + } + + /** + * Ensures that values specified at launch time are passed to {@link LaunchParamsModifier} + * when we are laying out a new task. + */ + @Test + public void testCreateTaskLayout() { + // modifier for validating passed values. + final LaunchParamsModifier modifier = mock(LaunchParamsModifier.class); + mService.mStackSupervisor.getLaunchParamsController().registerModifier(modifier); + + // add custom values to activity info to make unique. + final ActivityInfo info = new ActivityInfo(); + final Rect launchBounds = new Rect(0, 0, 20, 30); + + final WindowLayout windowLayout = + new WindowLayout(10, .5f, 20, 1.0f, Gravity.NO_GRAVITY, 1, 1); + + info.windowLayout = windowLayout; + info.applicationInfo = new ApplicationInfo(); + info.applicationInfo.packageName = ActivityBuilder.getDefaultComponent().getPackageName(); + + // create starter. + final ActivityStarter optionStarter = prepareStarter(0 /* launchFlags */); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchBounds(launchBounds); + + // run starter. + optionStarter + .setReason("testCreateTaskLayout") + .setActivityInfo(info) + .setActivityOptions(new SafeActivityOptions(options)) + .execute(); + + // verify that values are passed to the modifier. + verify(modifier, times(1)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options), + any(), any()); + } + + /** + * This test ensures that if the intent is being delivered to a + */ + @Test + public void testSplitScreenDeliverToTop() { + final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + final ActivityRecord focusActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .build(); + + focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + + final ActivityRecord reusableActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .build(); + + // Create reusable activity after entering split-screen so that it is the top secondary + // stack. + reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + + // Set focus back to primary. + mService.mStackSupervisor.setFocusStackUnchecked("testSplitScreenDeliverToTop", + focusActivity.getStack()); + + doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt()); + + final int result = starter.setReason("testSplitScreenDeliverToTop").execute(); + + // Ensure result is delivering intent to top. + assertEquals(result, START_DELIVERED_TO_TOP); + } + + /** + * This test ensures that if the intent is being delivered to a split-screen unfocused task + * reports it is brought to front instead of delivering to top. + */ + @Test + public void testSplitScreenTaskToFront() { + final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + // Create reusable activity here first. Setting the windowing mode of the primary stack + // will move the existing standard full screen stack to secondary, putting this one on the + // bottom. + final ActivityRecord reusableActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .build(); + + reusableActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + + final ActivityRecord focusActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .build(); + + // Enter split-screen. Primary stack should have focus. + focusActivity.getStack().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + + doReturn(reusableActivity).when(mService.mStackSupervisor).findTaskLocked(any(), anyInt()); + + final int result = starter.setReason("testSplitScreenMoveToFront").execute(); + + // Ensure result is moving task to front. + assertEquals(result, START_TASK_TO_FRONT); + } + + /** + * Tests activity is cleaned up properly in a task mode violation. + */ + @Test + public void testTaskModeViolation() { + final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay(); + assertNoTasks(display); + + final ActivityStarter starter = prepareStarter(0); + + final LockTaskController lockTaskController = mService.getLockTaskController(); + doReturn(true).when(lockTaskController).isLockTaskModeViolation(any()); + + final int result = starter.setReason("testTaskModeViolation").execute(); + + assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result); + assertNoTasks(display); + } + + private void assertNoTasks(ActivityDisplay display) { + for (int i = display.getChildCount() - 1; i >= 0; --i) { + final ActivityStack stack = display.getChildAt(i); + assertTrue(stack.getAllTasks().isEmpty()); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/am/ActivityTestsBase.java new file mode 100644 index 000000000000..d9a346b6c589 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ActivityTestsBase.java @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.app.ActivityOptions; +import android.app.IApplicationThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.os.HandlerThread; +import android.os.Looper; +import android.service.voice.IVoiceInteractionSession; +import android.support.test.InstrumentationRegistry; +import android.testing.DexmakerShareClassLoaderRule; + +import com.android.internal.app.IVoiceInteractor; +import com.android.server.AttributeCache; +import com.android.server.wm.AppWindowContainerController; +import com.android.server.wm.DisplayWindowController; +import com.android.server.wm.PinnedStackWindowController; +import com.android.server.wm.StackWindowController; +import com.android.server.wm.TaskWindowContainerController; +import com.android.server.wm.WindowManagerService; +import com.android.server.wm.WindowTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; + +/** + * A base class to handle common operations in activity related unit tests. + */ +public class ActivityTestsBase { + private static boolean sOneTimeSetupDone = false; + + @Rule + public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = + new DexmakerShareClassLoaderRule(); + + private final Context mContext = InstrumentationRegistry.getContext(); + private HandlerThread mHandlerThread; + + // Default package name + static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo"; + + // Default base activity name + private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity"; + + @Before + public void setUp() throws Exception { + if (!sOneTimeSetupDone) { + sOneTimeSetupDone = true; + MockitoAnnotations.initMocks(this); + } + mHandlerThread = new HandlerThread("ActivityTestsBaseThread"); + mHandlerThread.start(); + } + + @After + public void tearDown() { + mHandlerThread.quitSafely(); + } + + protected ActivityManagerService createActivityManagerService() { + final ActivityManagerService service = + setupActivityManagerService(new TestActivityManagerService(mContext)); + AttributeCache.init(mContext); + return service; + } + + protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) { + service = spy(service); + doReturn(mock(IPackageManager.class)).when(service).getPackageManager(); + doNothing().when(service).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt()); + service.mWindowManager = prepareMockWindowManager(); + return service; + } + + /** + * Builder for creating new activities. + */ + protected static class ActivityBuilder { + // An id appended to the end of the component name to make it unique + private static int sCurrentActivityId = 0; + + + + private final ActivityManagerService mService; + + private ComponentName mComponent; + private TaskRecord mTaskRecord; + private int mUid; + private boolean mCreateTask; + private ActivityStack mStack; + private int mActivityFlags; + + ActivityBuilder(ActivityManagerService service) { + mService = service; + } + + ActivityBuilder setComponent(ComponentName component) { + mComponent = component; + return this; + } + + static ComponentName getDefaultComponent() { + return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, + DEFAULT_COMPONENT_PACKAGE_NAME); + } + + ActivityBuilder setTask(TaskRecord task) { + mTaskRecord = task; + return this; + } + + ActivityBuilder setActivityFlags(int flags) { + mActivityFlags = flags; + return this; + } + + ActivityBuilder setStack(ActivityStack stack) { + mStack = stack; + return this; + } + + ActivityBuilder setCreateTask(boolean createTask) { + mCreateTask = createTask; + return this; + } + + ActivityBuilder setUid(int uid) { + mUid = uid; + return this; + } + + ActivityRecord build() { + if (mComponent == null) { + final int id = sCurrentActivityId++; + mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, + DEFAULT_COMPONENT_CLASS_NAME + id); + } + + if (mCreateTask) { + mTaskRecord = new TaskBuilder(mService.mStackSupervisor) + .setComponent(mComponent) + .setStack(mStack).build(); + } + + Intent intent = new Intent(); + intent.setComponent(mComponent); + final ActivityInfo aInfo = new ActivityInfo(); + aInfo.applicationInfo = new ApplicationInfo(); + aInfo.applicationInfo.packageName = mComponent.getPackageName(); + aInfo.applicationInfo.uid = mUid; + aInfo.flags |= mActivityFlags; + + final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, + 0 /* launchedFromPid */, 0, null, intent, null, + aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */, + 0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */, + mService.mStackSupervisor, null /* options */, null /* sourceRecord */); + activity.mWindowContainerController = mock(AppWindowContainerController.class); + + if (mTaskRecord != null) { + mTaskRecord.addActivityToTop(activity); + } + + activity.setProcess(new ProcessRecord(null, null, + mService.mContext.getApplicationInfo(), "name", 12345)); + activity.app.thread = mock(IApplicationThread.class); + + return activity; + } + } + + /** + * Builder for creating new tasks. + */ + protected static class TaskBuilder { + // Default package name + static final String DEFAULT_PACKAGE = "com.bar"; + + private final ActivityStackSupervisor mSupervisor; + + private ComponentName mComponent; + private String mPackage; + private int mFlags = 0; + private int mTaskId = 0; + private int mUserId = 0; + private IVoiceInteractionSession mVoiceSession; + private boolean mCreateStack = true; + + private ActivityStack mStack; + + TaskBuilder(ActivityStackSupervisor supervisor) { + mSupervisor = supervisor; + } + + TaskBuilder setComponent(ComponentName component) { + mComponent = component; + return this; + } + + TaskBuilder setPackage(String packageName) { + mPackage = packageName; + return this; + } + + /** + * Set to {@code true} by default, set to {@code false} to prevent the task from + * automatically creating a parent stack. + */ + TaskBuilder setCreateStack(boolean createStack) { + mCreateStack = createStack; + return this; + } + + TaskBuilder setVoiceSession(IVoiceInteractionSession session) { + mVoiceSession = session; + return this; + } + + TaskBuilder setFlags(int flags) { + mFlags = flags; + return this; + } + + TaskBuilder setTaskId(int taskId) { + mTaskId = taskId; + return this; + } + + TaskBuilder setUserId(int userId) { + mUserId = userId; + return this; + } + + TaskBuilder setStack(ActivityStack stack) { + mStack = stack; + return this; + } + + TaskRecord build() { + if (mStack == null && mCreateStack) { + mStack = mSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + } + + final ActivityInfo aInfo = new ActivityInfo(); + aInfo.applicationInfo = new ApplicationInfo(); + aInfo.applicationInfo.packageName = mPackage; + + Intent intent = new Intent(); + if (mComponent == null) { + mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, + DEFAULT_COMPONENT_CLASS_NAME); + } + + intent.setComponent(mComponent); + intent.setFlags(mFlags); + + final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo, + intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/); + task.userId = mUserId; + + if (mStack != null) { + mSupervisor.setFocusStackUnchecked("test", mStack); + mStack.addTask(task, true, "creating test task"); + task.setStack(mStack); + task.setWindowContainerController(); + } + + task.touchActiveTime(); + + return task; + } + + private static class TestTaskRecord extends TaskRecord { + TestTaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, + Intent _intent, IVoiceInteractionSession _voiceSession, + IVoiceInteractor _voiceInteractor) { + super(service, _taskId, info, _intent, _voiceSession, _voiceInteractor); + } + + @Override + void createWindowContainer(boolean onTop, boolean showForAllUsers) { + setWindowContainerController(); + } + + private void setWindowContainerController() { + setWindowContainerController(mock(TaskWindowContainerController.class)); + } + } + } + + /** + * An {@link ActivityManagerService} subclass which provides a test + * {@link ActivityStackSupervisor}. + */ + protected static class TestActivityManagerService extends ActivityManagerService { + private ClientLifecycleManager mLifecycleManager; + private LockTaskController mLockTaskController; + + TestActivityManagerService(Context context) { + super(context); + mSupportsMultiWindow = true; + mSupportsMultiDisplay = true; + mSupportsSplitScreenMultiWindow = true; + mSupportsFreeformWindowManagement = true; + mSupportsPictureInPicture = true; + mWindowManager = WindowTestUtils.getMockWindowManagerService(); + } + + @Override + public ClientLifecycleManager getLifecycleManager() { + if (mLifecycleManager == null) { + return super.getLifecycleManager(); + } + return mLifecycleManager; + } + + public LockTaskController getLockTaskController() { + if (mLockTaskController == null) { + mLockTaskController = spy(super.getLockTaskController()); + } + + return mLockTaskController; + } + + void setLifecycleManager(ClientLifecycleManager manager) { + mLifecycleManager = manager; + } + + @Override + final protected ActivityStackSupervisor createStackSupervisor() { + final ActivityStackSupervisor supervisor = spy(createTestSupervisor()); + final KeyguardController keyguardController = mock(KeyguardController.class); + + // No home stack is set. + doNothing().when(supervisor).moveHomeStackToFront(any()); + doReturn(true).when(supervisor).moveHomeStackTaskToTop(any()); + // Invoked during {@link ActivityStack} creation. + doNothing().when(supervisor).updateUIDsPresentOnDisplay(); + // Always keep things awake. + doReturn(true).when(supervisor).hasAwakeDisplay(); + // Called when moving activity to pinned stack. + doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), anyBoolean()); + // Do not schedule idle timeouts + doNothing().when(supervisor).scheduleIdleTimeoutLocked(any()); + // unit test version does not handle launch wake lock + doNothing().when(supervisor).acquireLaunchWakelock(); + doReturn(keyguardController).when(supervisor).getKeyguardController(); + + supervisor.initialize(); + + return supervisor; + } + + protected ActivityStackSupervisor createTestSupervisor() { + return new TestActivityStackSupervisor(this, mHandlerThread.getLooper()); + } + + @Override + void updateUsageStats(ActivityRecord component, boolean resumed) { + } + } + + /** + * An {@link ActivityStackSupervisor} which stubs out certain methods that depend on + * setup not available in the test environment. Also specifies an injector for + */ + protected static class TestActivityStackSupervisor extends ActivityStackSupervisor { + private ActivityDisplay mDisplay; + private KeyguardController mKeyguardController; + + public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) { + super(service, looper); + mDisplayManager = + (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE); + mWindowManager = prepareMockWindowManager(); + mKeyguardController = mock(KeyguardController.class); + } + + @Override + public void initialize() { + super.initialize(); + mDisplay = spy(new TestActivityDisplay(this, DEFAULT_DISPLAY)); + attachDisplay(mDisplay); + } + + @Override + public KeyguardController getKeyguardController() { + return mKeyguardController; + } + + @Override + ActivityDisplay getDefaultDisplay() { + return mDisplay; + } + + // Just return the current front task. This is called internally so we cannot use spy to mock this out. + @Override + ActivityStack getNextFocusableStackLocked(ActivityStack currentFocus, + boolean ignoreCurrent) { + return mFocusedStack; + } + } + + protected static class TestActivityDisplay extends ActivityDisplay { + + private final ActivityStackSupervisor mSupervisor; + TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) { + super(supervisor, displayId); + mSupervisor = supervisor; + } + + @Override + T createStackUnchecked(int windowingMode, int activityType, + int stackId, boolean onTop) { + if (windowingMode == WINDOWING_MODE_PINNED) { + return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) { + @Override + Rect getDefaultPictureInPictureBounds(float aspectRatio) { + return new Rect(50, 50, 100, 100); + } + + @Override + PinnedStackWindowController createStackWindowController(int displayId, + boolean onTop, Rect outBounds) { + return mock(PinnedStackWindowController.class); + } + }; + } else { + return (T) new TestActivityStack( + this, stackId, mSupervisor, windowingMode, activityType, onTop); + } + } + + @Override + protected DisplayWindowController createWindowContainerController() { + return mock(DisplayWindowController.class); + } + } + + private static WindowManagerService prepareMockWindowManager() { + final WindowManagerService service = WindowTestUtils.getMockWindowManagerService(); + + doAnswer((InvocationOnMock invocationOnMock) -> { + final Runnable runnable = invocationOnMock.getArgument(0); + if (runnable != null) { + runnable.run(); + } + return null; + }).when(service).inSurfaceTransaction(any()); + + return service; + } + + /** + * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a + * method is called. Note that its functionality depends on the implementations of the + * construction arguments. + */ + protected static class TestActivityStack + extends ActivityStack { + private int mOnActivityRemovedFromStackCount = 0; + private T mContainerController; + + static final int IS_TRANSLUCENT_UNSET = 0; + static final int IS_TRANSLUCENT_FALSE = 1; + static final int IS_TRANSLUCENT_TRUE = 2; + private int mIsTranslucent = IS_TRANSLUCENT_UNSET; + + static final int SUPPORTS_SPLIT_SCREEN_UNSET = 0; + static final int SUPPORTS_SPLIT_SCREEN_FALSE = 1; + static final int SUPPORTS_SPLIT_SCREEN_TRUE = 2; + private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET; + + TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor, + int windowingMode, int activityType, boolean onTop) { + super(display, stackId, supervisor, windowingMode, activityType, onTop); + } + + @Override + void onActivityRemovedFromStack(ActivityRecord r) { + mOnActivityRemovedFromStackCount++; + super.onActivityRemovedFromStack(r); + } + + // Returns the number of times {@link #onActivityRemovedFromStack} has been called + int onActivityRemovedFromStackInvocationCount() { + return mOnActivityRemovedFromStackCount; + } + + @Override + protected T createStackWindowController(int displayId, boolean onTop, Rect outBounds) { + mContainerController = (T) WindowTestUtils.createMockStackWindowContainerController(); + + // Primary pinned stacks require a non-empty out bounds to be set or else all tasks + // will be moved to the full screen stack. + if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + outBounds.set(0, 0, 100, 100); + } + return mContainerController; + } + + @Override + T getWindowContainerController() { + return mContainerController; + } + + void setIsTranslucent(boolean isTranslucent) { + mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE; + } + + @Override + boolean isStackTranslucent(ActivityRecord starting) { + switch (mIsTranslucent) { + case IS_TRANSLUCENT_TRUE: + return true; + case IS_TRANSLUCENT_FALSE: + return false; + case IS_TRANSLUCENT_UNSET: + default: + return super.isStackTranslucent(starting); + } + } + + void setSupportsSplitScreen(boolean supportsSplitScreen) { + mSupportsSplitScreen = supportsSplitScreen + ? SUPPORTS_SPLIT_SCREEN_TRUE : SUPPORTS_SPLIT_SCREEN_FALSE; + } + + @Override + public boolean supportsSplitScreenWindowingMode() { + switch (mSupportsSplitScreen) { + case SUPPORTS_SPLIT_SCREEN_TRUE: + return true; + case SUPPORTS_SPLIT_SCREEN_FALSE: + return false; + case SUPPORTS_SPLIT_SCREEN_UNSET: + default: + return super.supportsSplitScreenWindowingMode(); + } + } + + @Override + void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, + boolean newTask, boolean keepCurTransition, + ActivityOptions options) { + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/wmtests/src/com/android/server/am/AppErrorDialogTest.java new file mode 100644 index 000000000000..2289dfc08d0c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/AppErrorDialogTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.content.Context; +import android.os.Handler; +import android.support.test.InstrumentationRegistry; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.AppOpsService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Tests for {@link AppErrorDialog}. + * + * Build/Install/Run: + * atest WmTests:AppErrorDialogTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AppErrorDialogTest { + + private Context mContext; + private ActivityManagerService mService; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mService = new ActivityManagerService(new ActivityManagerService.Injector() { + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return null; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return null; + } + + @Override + public boolean isNetworkRestrictedForUid(int uid) { + return false; + } + }); + } + + @Test + @UiThreadTest + public void testCreateWorks() throws Exception { + AppErrorDialog.Data data = new AppErrorDialog.Data(); + data.proc = new ProcessRecord(null, null, mContext.getApplicationInfo(), "name", 12345); + data.result = new AppErrorResult(); + + AppErrorDialog dialog = new AppErrorDialog(mContext, mService, data); + + dialog.create(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/am/AssistDataRequesterTest.java new file mode 100644 index 000000000000..285a84f5972b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/AssistDataRequesterTest.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_ERRORED; +import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT; +import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE; +import static android.graphics.Bitmap.Config.ARGB_8888; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.app.AppOpsManager; +import android.app.IActivityManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; +import android.view.IWindowManager; + +import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Note: Currently, we only support fetching the screenshot for the current application, so the + * screenshot checks are hardcoded accordingly. + * + * Build/Install/Run: + * atest WmTests:AssistDataRequesterTest + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class AssistDataRequesterTest extends ActivityTestsBase { + + private static final String TAG = AssistDataRequesterTest.class.getSimpleName(); + + private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true; + private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true; + private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true; + private static final boolean FETCH_DATA = true; + private static final boolean FETCH_SCREENSHOTS = true; + private static final boolean ALLOW_FETCH_DATA = true; + private static final boolean ALLOW_FETCH_SCREENSHOTS = true; + + private static final int TEST_UID = 0; + private static final String TEST_PACKAGE = ""; + + private Context mContext; + private AssistDataRequester mDataRequester; + private Callbacks mCallbacks; + private Object mCallbacksLock; + private Handler mHandler; + private IActivityManager mAm; + private IWindowManager mWm; + private AppOpsManager mAppOpsManager; + + /** + * The requests to fetch assist data are done incrementally from the text thread, and we + * immediately post onto the main thread handler below, which would immediately make the + * callback and decrement the pending counts. In order to assert the pending counts, we defer + * the callbacks on the test-side until after we flip the gate, after which we can drain the + * main thread handler and make assertions on the actual callbacks + */ + private CountDownLatch mGate; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mAm = mock(IActivityManager.class); + mWm = mock(IWindowManager.class); + mAppOpsManager = mock(AppOpsManager.class); + mContext = InstrumentationRegistry.getContext(); + mHandler = new Handler(Looper.getMainLooper()); + mCallbacksLock = new Object(); + mCallbacks = new Callbacks(); + mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks, + mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT); + + // Gate the continuation of the assist data callbacks until we are ready within the tests + mGate = new CountDownLatch(1); + doAnswer(invocation -> { + mHandler.post(() -> { + try { + mGate.await(10, TimeUnit.SECONDS); + mDataRequester.onHandleAssistData(new Bundle()); + } catch (InterruptedException e) { + Log.e(TAG, "Failed to wait", e); + } + }); + return true; + }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(), + anyBoolean()); + doAnswer(invocation -> { + mHandler.post(() -> { + try { + mGate.await(10, TimeUnit.SECONDS); + mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1, ARGB_8888)); + } catch (InterruptedException e) { + Log.e(TAG, "Failed to wait", e); + } + }); + return true; + }).when(mWm).requestAssistScreenshot(any()); + } + + private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed, + boolean assistScreenshotAllowed) throws Exception { + doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity(); + doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager) + .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString()); + doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager) + .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString()); + } + + @Test + public void testRequestData() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertReceivedDataCount(5, 5, 1, 1); + } + + @Test + public void testEmptyActivities_expectNoCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertReceivedDataCount(0, 0, 0, 0); + } + + @Test + public void testCurrentAppDisallow_expectNullCallbacks() throws Exception { + setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertReceivedDataCount(0, 1, 0, 1); + } + + @Test + public void testProcessPendingData() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mCallbacks.canHandleReceivedData = false; + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertTrue(mDataRequester.getPendingDataCount() == 5); + assertTrue(mDataRequester.getPendingScreenshotCount() == 1); + mGate.countDown(); + waitForIdle(mHandler); + + // Callbacks still not ready to receive, but all pending data is received + assertTrue(mDataRequester.getPendingDataCount() == 0); + assertTrue(mDataRequester.getPendingScreenshotCount() == 0); + assertTrue(mCallbacks.receivedData.isEmpty()); + assertTrue(mCallbacks.receivedScreenshots.isEmpty()); + assertFalse(mCallbacks.requestCompleted); + + mCallbacks.canHandleReceivedData = true; + mDataRequester.processPendingAssistData(); + // Since we are posting the callback for the request-complete, flush the handler as well + mGate.countDown(); + waitForIdle(mHandler); + assertTrue(mCallbacks.receivedData.size() == 5); + assertTrue(mCallbacks.receivedScreenshots.size() == 1); + assertTrue(mCallbacks.requestCompleted); + + // Clear the state and ensure that we only process pending data once + mCallbacks.reset(); + mDataRequester.processPendingAssistData(); + assertTrue(mCallbacks.receivedData.isEmpty()); + assertTrue(mCallbacks.receivedScreenshots.isEmpty()); + } + + @Test + public void testNoFetchData_expectNoDataCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertReceivedDataCount(0, 0, 0, 1); + } + + @Test + public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + // Expect a single null data when the appops is denied + assertReceivedDataCount(0, 1, 0, 1); + } + + @Test + public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), + anyBoolean(), anyBoolean()); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + // Expect a single null data when requestAssistContextExtras() fails + assertReceivedDataCount(0, 1, 0, 1); + } + + @Test + public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertReceivedDataCount(5, 5, 0, 0); + } + + @Test + public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + !CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + // Expect a single null screenshot when the appops is denied + assertReceivedDataCount(5, 5, 0, 1); + } + + @Test + public void testCanNotHandleReceivedData_expectNoCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED, + !CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mCallbacks.canHandleReceivedData = false; + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + mGate.countDown(); + waitForIdle(mHandler); + assertTrue(mCallbacks.receivedData.isEmpty()); + assertTrue(mCallbacks.receivedScreenshots.isEmpty()); + } + + @Test + public void testRequestDataNoneAllowed_expectNullCallbacks() throws Exception { + setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, + CALLER_ASSIST_SCREENSHOT_ALLOWED); + + mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS, + !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE); + assertReceivedDataCount(0, 1, 0, 1); + } + + private void assertReceivedDataCount(int numPendingData, int numReceivedData, + int numPendingScreenshots, int numReceivedScreenshots) throws Exception { + assertTrue("Expected " + numPendingData + " pending data, got " + + mDataRequester.getPendingDataCount(), + mDataRequester.getPendingDataCount() == numPendingData); + assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got " + + mDataRequester.getPendingScreenshotCount(), + mDataRequester.getPendingScreenshotCount() == numPendingScreenshots); + assertFalse("Expected request NOT completed", mCallbacks.requestCompleted); + mGate.countDown(); + waitForIdle(mHandler); + assertTrue("Expected " + numReceivedData + " data, received " + + mCallbacks.receivedData.size(), + mCallbacks.receivedData.size() == numReceivedData); + assertTrue("Expected " + numReceivedScreenshots + " screenshots, received " + + mCallbacks.receivedScreenshots.size(), + mCallbacks.receivedScreenshots.size() == numReceivedScreenshots); + assertTrue("Expected request completed", mCallbacks.requestCompleted); + } + + private List createActivityList(int size) { + ArrayList activities = new ArrayList<>(); + for (int i = 0; i < size; i++) { + activities.add(mock(IBinder.class)); + } + return activities; + } + + public void waitForIdle(Handler h) throws Exception { + if (Looper.myLooper() == h.getLooper()) { + throw new RuntimeException("This method can not be called from the waiting looper"); + } + CountDownLatch latch = new CountDownLatch(1); + h.post(() -> latch.countDown()); + latch.await(2, TimeUnit.SECONDS); + } + + private class Callbacks implements AssistDataRequesterCallbacks { + + boolean canHandleReceivedData = true; + boolean requestCompleted = false; + ArrayList receivedData = new ArrayList<>(); + ArrayList receivedScreenshots = new ArrayList<>(); + + void reset() { + canHandleReceivedData = true; + receivedData.clear(); + receivedScreenshots.clear(); + } + + @Override + public boolean canHandleReceivedAssistDataLocked() { + return canHandleReceivedData; + } + + @Override + public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) { + receivedData.add(data); + } + + @Override + public void onAssistScreenshotReceivedLocked(Bitmap screenshot) { + receivedScreenshots.add(screenshot); + } + + @Override + public void onAssistRequestCompleted() { + mHandler.post(() -> { + try { + mGate.await(10, TimeUnit.SECONDS); + requestCompleted = true; + } catch (InterruptedException e) { + Log.e(TAG, "Failed to wait", e); + } + }); + } + } +} \ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/am/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/am/ClientLifecycleManagerTests.java new file mode 100644 index 000000000000..3098db267a74 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/ClientLifecycleManagerTests.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.IApplicationThread; +import android.app.servertransaction.ClientTransaction; +import android.os.Binder; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link ClientLifecycleManager}. + * + * Build/Install/Run: + * atest WmTests:ClientLifecycleManagerTests + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class ClientLifecycleManagerTests { + + @Test + public void testScheduleAndRecycleBinderClientTransaction() throws Exception { + ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class), + new Binder())); + + ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); + clientLifecycleManager.scheduleTransaction(item); + + verify(item, times(1)).recycle(); + } + + @Test + public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception { + ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class), + new Binder())); + + ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager(); + clientLifecycleManager.scheduleTransaction(item); + + verify(item, times(0)).recycle(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/wmtests/src/com/android/server/am/CoreSettingsObserverTest.java new file mode 100644 index 000000000000..adf3f940820a --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/CoreSettingsObserverTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static com.android.server.am.ActivityManagerService.Injector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.test.mock.MockContentResolver; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.AppOpsService; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; + +/** + * Test class for {@link CoreSettingsObserver}. + * + * Build/Install/Run: + * atest WmTests:CoreSettingsObserverTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class CoreSettingsObserverTest { + private static final String TEST_SETTING_SECURE_INT = "secureInt"; + private static final String TEST_SETTING_GLOBAL_FLOAT = "globalFloat"; + private static final String TEST_SETTING_SYSTEM_STRING = "systemString"; + + private static final int TEST_INT = 111; + private static final float TEST_FLOAT = 3.14f; + private static final String TEST_STRING = "testString"; + + private ActivityManagerService mAms; + @Mock private Context mContext; + + private MockContentResolver mContentResolver; + private CoreSettingsObserver mCoreSettingsObserver; + + @BeforeClass + public static void setupOnce() { + FakeSettingsProvider.clearSettingsProvider(); + CoreSettingsObserver.sSecureSettingToTypeMap.put(TEST_SETTING_SECURE_INT, int.class); + CoreSettingsObserver.sGlobalSettingToTypeMap.put(TEST_SETTING_GLOBAL_FLOAT, float.class); + CoreSettingsObserver.sSystemSettingToTypeMap.put(TEST_SETTING_SYSTEM_STRING, String.class); + } + + @AfterClass + public static void tearDownOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final Context originalContext = InstrumentationRegistry.getContext(); + when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo()); + mContentResolver = new MockContentResolver(mContext); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + + mAms = new ActivityManagerService(new TestInjector()); + mCoreSettingsObserver = new CoreSettingsObserver(mAms); + } + + @Test + public void testPopulateSettings() { + Settings.Secure.putInt(mContentResolver, TEST_SETTING_SECURE_INT, TEST_INT); + Settings.Global.putFloat(mContentResolver, TEST_SETTING_GLOBAL_FLOAT, TEST_FLOAT); + Settings.System.putString(mContentResolver, TEST_SETTING_SYSTEM_STRING, TEST_STRING); + + final Bundle settingsBundle = getPopulatedBundle(); + + assertEquals("Unexpected value of " + TEST_SETTING_SECURE_INT, + TEST_INT, settingsBundle.getInt(TEST_SETTING_SECURE_INT)); + assertEquals("Unexpected value of " + TEST_SETTING_GLOBAL_FLOAT, + TEST_FLOAT, settingsBundle.getFloat(TEST_SETTING_GLOBAL_FLOAT), 0); + assertEquals("Unexpected value of " + TEST_SETTING_SYSTEM_STRING, + TEST_STRING, settingsBundle.getString(TEST_SETTING_SYSTEM_STRING)); + } + + @Test + public void testPopulateSettings_settingNotSet() { + final Bundle settingsBundle = getPopulatedBundle(); + + assertFalse("Bundle should not contain " + TEST_SETTING_SECURE_INT, + settingsBundle.containsKey(TEST_SETTING_SECURE_INT)); + assertFalse("Bundle should not contain " + TEST_SETTING_GLOBAL_FLOAT, + settingsBundle.containsKey(TEST_SETTING_GLOBAL_FLOAT)); + assertFalse("Bundle should not contain " + TEST_SETTING_SYSTEM_STRING, + settingsBundle.containsKey(TEST_SETTING_SYSTEM_STRING)); + } + + private Bundle getPopulatedBundle() { + final Bundle settingsBundle = new Bundle(); + mCoreSettingsObserver.populateSettings(settingsBundle, + CoreSettingsObserver.sGlobalSettingToTypeMap); + mCoreSettingsObserver.populateSettings(settingsBundle, + CoreSettingsObserver.sSecureSettingToTypeMap); + mCoreSettingsObserver.populateSettings(settingsBundle, + CoreSettingsObserver.sSystemSettingToTypeMap); + return settingsBundle; + } + + private class TestInjector extends Injector { + @Override + public Context getContext() { + return mContext; + } + + public AppOpsService getAppOpsService(File file, Handler handler) { + return null; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return null; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/DummyAmTests.java b/services/tests/wmtests/src/com/android/server/am/DummyAmTests.java deleted file mode 100644 index 023e4ab6636f..000000000000 --- a/services/tests/wmtests/src/com/android/server/am/DummyAmTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Test; - -import androidx.test.filters.FlakyTest; - -/** - * Dummy test for com.android.server.am. - * TODO(b/113800711): Remove this class once the actual tests are moved from servicestests. - */ -public class DummyAmTests { - - @Presubmit - @Test - public void preSubmitTest() {} - - @FlakyTest - @Presubmit - @Test - public void flakyPreSubmitTest() {} - - @Test - public void postSubmitTest() {} - - @FlakyTest - @Test - public void flakyPostSubmitTest() {} -} diff --git a/services/tests/wmtests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java b/services/tests/wmtests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java new file mode 100644 index 000000000000..b1e0b51366bd --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/GlobalSettingsToPropertiesMapperTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import android.content.ContentResolver; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.test.mock.MockContentResolver; + +import com.android.internal.util.Preconditions; +import com.android.internal.util.test.FakeSettingsProvider; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; + +/** + * Tests for {@link GlobalSettingsToPropertiesMapper} + * + * Build/Install/Run: + * atest WmTests:GlobalSettingsToPropertiesMapperTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class GlobalSettingsToPropertiesMapperTest { + private static final String[][] TEST_MAPPING = new String[][] { + {Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "TestProperty"} + }; + + private TestMapper mTestMapper; + private MockContentResolver mMockContentResolver; + + @Before + public void setup() { + // Use FakeSettingsProvider to not affect global state + mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getContext()); + mMockContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + mTestMapper = new TestMapper(mMockContentResolver); + } + + @Test + public void testUpdatePropertiesFromGlobalSettings() { + Settings.Global.putString(mMockContentResolver, + Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue"); + + mTestMapper.updatePropertiesFromGlobalSettings(); + String propValue = mTestMapper.systemPropertiesGet("TestProperty"); + Assert.assertEquals("testValue", propValue); + + Settings.Global.putString(mMockContentResolver, + Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, "testValue2"); + mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, + "TestProperty"); + propValue = mTestMapper.systemPropertiesGet("TestProperty"); + Assert.assertEquals("testValue2", propValue); + + Settings.Global.putString(mMockContentResolver, + Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, null); + mTestMapper.updatePropertyFromSetting(Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS, + "TestProperty"); + propValue = mTestMapper.systemPropertiesGet("TestProperty"); + Assert.assertEquals("", propValue); + } + + @Test + public void testUpdatePropertiesFromGlobalSettings_PropertyAndSettingNotPresent() { + // Test that empty property will not not be set if setting is not set + mTestMapper.updatePropertiesFromGlobalSettings(); + String propValue = mTestMapper.systemPropertiesGet("TestProperty"); + Assert.assertNull("Property should not be set if setting is null", propValue); + } + + private static class TestMapper extends GlobalSettingsToPropertiesMapper { + private final Map mProps = new HashMap<>(); + + TestMapper(ContentResolver contentResolver) { + super(contentResolver, TEST_MAPPING); + } + + @Override + protected String systemPropertiesGet(String key) { + Preconditions.checkNotNull(key); + return mProps.get(key); + } + + @Override + protected void systemPropertiesSet(String key, String value) { + Preconditions.checkNotNull(value); + Preconditions.checkNotNull(key); + mProps.put(key, value); + } + } + +} + diff --git a/services/tests/wmtests/src/com/android/server/am/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/am/LaunchParamsControllerTests.java new file mode 100644 index 000000000000..5f2bc4d3fea4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/LaunchParamsControllerTests.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; +import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE; +import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo.WindowLayout; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.am.LaunchParamsController.LaunchParams; +import com.android.server.am.LaunchParamsController.LaunchParamsModifier; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for exercising {@link LaunchParamsController}. + * + * Build/Install/Run: + * atest WmTests:LaunchParamsControllerTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LaunchParamsControllerTests extends ActivityTestsBase { + private ActivityManagerService mService; + private LaunchParamsController mController; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mService = createActivityManagerService(); + mController = new LaunchParamsController(mService); + } + + /** + * Makes sure positioners get values passed to controller. + */ + @Test + public void testArgumentPropagation() { + final LaunchParamsModifier + positioner = mock(LaunchParamsModifier.class); + mController.registerModifier(positioner); + + final ActivityRecord record = new ActivityBuilder(mService).build(); + final ActivityRecord source = new ActivityBuilder(mService).build(); + final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0); + final ActivityOptions options = mock(ActivityOptions.class); + + mController.calculate(record.getTask(), layout, record, source, options, + new LaunchParams()); + verify(positioner, times(1)).onCalculate(eq(record.getTask()), eq(layout), eq(record), + eq(source), eq(options), any(), any()); + } + + /** + * Ensures positioners further down the chain are not called when RESULT_DONE is returned. + */ + @Test + public void testEarlyExit() { + final LaunchParamsModifier + ignoredPositioner = mock(LaunchParamsModifier.class); + final LaunchParamsModifier earlyExitPositioner = + (task, layout, activity, source, options, currentParams, outParams) -> RESULT_DONE; + + mController.registerModifier(ignoredPositioner); + mController.registerModifier(earlyExitPositioner); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, + null /*source*/, null /*options*/, new LaunchParams()); + verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(), + any(), any()); + } + + /** + * Ensures that positioners are called in the correct order. + */ + @Test + public void testRegistration() { + LaunchParamsModifier earlyExitPositioner = + new InstrumentedPositioner(RESULT_DONE, new LaunchParams()); + + final LaunchParamsModifier firstPositioner = spy(earlyExitPositioner); + + mController.registerModifier(firstPositioner); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, + null /*source*/, null /*options*/, new LaunchParams()); + verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), + any()); + + final LaunchParamsModifier secondPositioner = spy(earlyExitPositioner); + + mController.registerModifier(secondPositioner); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, + null /*source*/, null /*options*/, new LaunchParams()); + verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), + any()); + verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), + any()); + } + + /** + * Makes sure positioners further down the registration chain are called. + */ + @Test + public void testPassThrough() { + final LaunchParamsModifier + positioner1 = mock(LaunchParamsModifier.class); + final LaunchParams params = new LaunchParams(); + params.mWindowingMode = WINDOWING_MODE_FREEFORM; + params.mBounds.set(0, 0, 30, 20); + params.mPreferredDisplayId = 3; + + final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE, + params); + + mController.registerModifier(positioner1); + mController.registerModifier(positioner2); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, new LaunchParams()); + + verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(), + eq(positioner2.getLaunchParams()), any()); + } + + /** + * Ensures skipped results are not propagated. + */ + @Test + public void testSkip() { + final LaunchParams params1 = new LaunchParams(); + params1.mBounds.set(0, 0, 10, 10); + final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1); + + final LaunchParams params2 = new LaunchParams(); + params2.mBounds.set(0, 0, 20, 30); + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, params2); + + mController.registerModifier(positioner1); + mController.registerModifier(positioner2); + + final LaunchParams + result = new LaunchParams(); + + mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, + null /*options*/, result); + + assertEquals(result, positioner2.getLaunchParams()); + } + + /** + * Ensures that {@link LaunchParamsModifier} requests specifying display id during + * layout are honored. + */ + @Test + public void testLayoutTaskPreferredDisplayChange() { + final LaunchParams params = new LaunchParams(); + params.mPreferredDisplayId = 2; + final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor).build(); + + mController.registerModifier(positioner); + + doNothing().when(mService).moveStackToDisplay(anyInt(), anyInt()); + mController.layoutTask(task, null /* windowLayout */); + verify(mService, times(1)).moveStackToDisplay(eq(task.getStackId()), + eq(params.mPreferredDisplayId)); + } + + /** + * Ensures that {@link LaunchParamsModifier} requests specifying windowingMode during + * layout are honored. + */ + @Test + public void testLayoutTaskWindowingModeChange() { + final LaunchParams params = new LaunchParams(); + final int windowingMode = WINDOWING_MODE_FREEFORM; + params.mWindowingMode = windowingMode; + final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor).build(); + + mController.registerModifier(positioner); + + final int beforeWindowMode = task.getStack().getWindowingMode(); + assertNotEquals(beforeWindowMode, windowingMode); + + mController.layoutTask(task, null /* windowLayout */); + + final int afterWindowMode = task.getStack().getWindowingMode(); + assertEquals(afterWindowMode, windowingMode); + } + + public static class InstrumentedPositioner implements + LaunchParamsModifier { + + final private int mReturnVal; + final private LaunchParams mParams; + + InstrumentedPositioner(int returnVal, LaunchParams params) { + mReturnVal = returnVal; + mParams = params; + } + + @Override + public int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityRecord source, ActivityOptions options, + LaunchParams currentParams, LaunchParams outParams) { + outParams.set(mParams); + return mReturnVal; + } + + LaunchParams getLaunchParams() { + return mParams; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/am/LockTaskControllerTest.java new file mode 100644 index 000000000000..a2e976922b7b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/LockTaskControllerTest.java @@ -0,0 +1,691 @@ +/* + * Copyright 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; +import static android.app.StatusBarManager.DISABLE2_MASK; +import static android.app.StatusBarManager.DISABLE2_NONE; +import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; +import static android.app.StatusBarManager.DISABLE_HOME; +import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS; +import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; +import static android.os.Process.SYSTEM_UID; +import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT; + +import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_LOCKED; +import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_PINNED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.StatusBarManager; +import android.app.admin.DevicePolicyManager; +import android.app.admin.IDevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.telecom.TelecomManager; +import android.testing.DexmakerShareClassLoaderRule; +import android.util.Pair; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.widget.LockPatternUtils; +import com.android.server.LocalServices; +import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.wm.WindowManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.verification.VerificationMode; + +/** + * Unit tests for {@link LockTaskController}. + * + * Build/Install/Run: + * bit WmTests:LockTaskControllerTest + */ +@Presubmit +@SmallTest +public class LockTaskControllerTest { + private static final String TEST_PACKAGE_NAME = "com.test.package"; + private static final String TEST_PACKAGE_NAME_2 = "com.test.package2"; + private static final String TEST_CLASS_NAME = ".TestClass"; + private static final int TEST_USER_ID = 123; + private static final int TEST_UID = 10467; + + @Rule + public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = + new DexmakerShareClassLoaderRule(); + + @Mock private ActivityStackSupervisor mSupervisor; + @Mock private IDevicePolicyManager mDevicePolicyManager; + @Mock private IStatusBarService mStatusBarService; + @Mock private WindowManagerService mWindowManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + @Mock private TelecomManager mTelecomManager; + @Mock private RecentTasks mRecentTasks; + + private LockTaskController mLockTaskController; + private Context mContext; + private String mLockToAppSetting; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getTargetContext(); + mLockToAppSetting = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mSupervisor.mRecentTasks = mRecentTasks; + + mLockTaskController = new LockTaskController(mContext, mSupervisor, + new ImmediatelyExecuteHandler()); + mLockTaskController.setWindowManager(mWindowManager); + mLockTaskController.mStatusBarService = mStatusBarService; + mLockTaskController.mDevicePolicyManager = mDevicePolicyManager; + mLockTaskController.mTelecomManager = mTelecomManager; + mLockTaskController.mLockPatternUtils = mLockPatternUtils; + + LocalServices.removeServiceForTest(StatusBarManagerInternal.class); + LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); + } + + @After + public void tearDown() throws Exception { + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, mLockToAppSetting); + } + + @Test + public void testPreconditions() { + // GIVEN nothing has happened + + // THEN current lock task mode should be NONE + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + } + + @Test + public void testStartLockTaskMode_once() throws Exception { + // GIVEN a task record with whitelisted auth + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + + // WHEN calling setLockTaskMode for LOCKED mode without resuming + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN the lock task mode state should be LOCKED + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + // THEN the task should be locked + assertTrue(mLockTaskController.isTaskLocked(tr)); + + // THEN lock task mode should be started + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); + } + + @Test + public void testStartLockTaskMode_twice() throws Exception { + // GIVEN two task records with whitelisted auth + TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + + // WHEN calling setLockTaskMode for LOCKED mode on both tasks + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + + // THEN the lock task mode state should be LOCKED + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + // THEN neither of the tasks should be able to move to back of stack + assertTrue(mLockTaskController.isTaskLocked(tr1)); + assertTrue(mLockTaskController.isTaskLocked(tr2)); + + // THEN lock task mode should be started + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); + } + + @Test + public void testStartLockTaskMode_pinningRequest() throws Exception { + // GIVEN a task record that is not whitelisted, i.e. with pinned auth + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); + + // WHEN calling startLockTaskMode + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN a pinning request should be shown + verify(mStatusBarManagerInternal).showScreenPinningRequest(anyInt()); + } + + @Test + public void testStartLockTaskMode_pinnedBySystem() throws Exception { + // GIVEN a task record with pinned auth + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); + + // WHEN the system calls startLockTaskMode + mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID); + + // THEN the lock task mode state should be PINNED + assertEquals(LOCK_TASK_MODE_PINNED, mLockTaskController.getLockTaskModeState()); + // THEN the task should be locked + assertTrue(mLockTaskController.isTaskLocked(tr)); + + // THEN lock task mode should be started + verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE); + // THEN screen pinning toast should be shown + verify(mStatusBarService).showPinningEnterExitToast(true /* entering */); + } + + @Test + public void testLockTaskViolation() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN it's not a lock task violation to try and launch this task without clearing + assertFalse(mLockTaskController.isLockTaskModeViolation(tr, false)); + + // THEN it's a lock task violation to launch another task that is not whitelisted + assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_PINNABLE))); + // THEN it's a lock task violation to launch another task that is disallowed from lock task + assertTrue(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_DONT_LOCK))); + + // THEN it's no a lock task violation to launch another task that is whitelisted + assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_WHITELISTED))); + assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE))); + // THEN it's not a lock task violation to launch another task that is priv launchable + assertFalse(mLockTaskController.isLockTaskModeViolation(getTaskRecord( + TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV))); + } + + @Test + public void testLockTaskViolation_emergencyCall() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // GIVEN tasks necessary for emergency calling + TaskRecord keypad = getTaskRecord(new Intent().setComponent(EMERGENCY_DIALER_COMPONENT), + TaskRecord.LOCK_TASK_AUTH_PINNABLE); + TaskRecord callAction = getTaskRecord(new Intent(Intent.ACTION_CALL_EMERGENCY), + TaskRecord.LOCK_TASK_AUTH_PINNABLE); + TaskRecord dialer = getTaskRecord("com.example.dialer", TaskRecord.LOCK_TASK_AUTH_PINNABLE); + when(mTelecomManager.getSystemDialerPackage()) + .thenReturn(dialer.intent.getComponent().getPackageName()); + + // GIVEN keyguard is allowed for lock task mode + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD); + + // THEN the above tasks should all be allowed + assertFalse(mLockTaskController.isLockTaskModeViolation(keypad)); + assertFalse(mLockTaskController.isLockTaskModeViolation(callAction)); + assertFalse(mLockTaskController.isLockTaskModeViolation(dialer)); + + // GIVEN keyguard is disallowed for lock task mode (default) + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE); + + // THEN the above tasks should all be blocked + assertTrue(mLockTaskController.isLockTaskModeViolation(keypad)); + assertTrue(mLockTaskController.isLockTaskModeViolation(callAction)); + assertTrue(mLockTaskController.isLockTaskModeViolation(dialer)); + } + + @Test + public void testStopLockTaskMode() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN the same caller calls stopLockTaskMode + mLockTaskController.stopLockTaskMode(tr, false, TEST_UID); + + // THEN the lock task mode should be NONE + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + // THEN the task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr)); + // THEN lock task mode should have been finished + verifyLockTaskStopped(times(1)); + } + + @Test(expected = SecurityException.class) + public void testStopLockTaskMode_differentCaller() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN a different caller calls stopLockTaskMode + mLockTaskController.stopLockTaskMode(tr, false, TEST_UID + 1); + + // THEN security exception should be thrown, because different caller tried to unlock + } + + @Test + public void testStopLockTaskMode_systemCaller() throws Exception { + // GIVEN one task record with whitelisted auth that is in lock task mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // WHEN system calls stopLockTaskMode + mLockTaskController.stopLockTaskMode(tr, true, SYSTEM_UID); + + // THEN lock task mode should still be active + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + } + + @Test + public void testStopLockTaskMode_twoTasks() throws Exception { + // GIVEN two task records with whitelisted auth that is in lock task mode + TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + + // WHEN calling stopLockTaskMode + mLockTaskController.stopLockTaskMode(tr2, false, TEST_UID); + + // THEN the lock task mode should still be active + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + // THEN the first task should still be locked + assertTrue(mLockTaskController.isTaskLocked(tr1)); + // THEN the top task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr2)); + // THEN lock task mode should not have been finished + verifyLockTaskStopped(never()); + } + + @Test + public void testStopLockTaskMode_rootTask() throws Exception { + // GIVEN two task records with whitelisted auth that is in lock task mode + TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + + // WHEN calling stopLockTaskMode on the root task + mLockTaskController.stopLockTaskMode(tr1, false, TEST_UID); + + // THEN the lock task mode should be inactive + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + // THEN the first task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr1)); + // THEN the top task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr2)); + // THEN lock task mode should be finished + verifyLockTaskStopped(times(1)); + } + + @Test + public void testStopLockTaskMode_pinned() throws Exception { + // GIVEN one task records that is in pinned mode + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE); + mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID); + // GIVEN that the keyguard is required to show after unlocking + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1); + + // reset invocation counter + reset(mStatusBarService); + + // WHEN calling stopLockTask + mLockTaskController.stopLockTaskMode(null, true, SYSTEM_UID); + + // THEN the lock task mode should no longer be active + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + // THEN the task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr)); + // THEN lock task mode should have been finished + verifyLockTaskStopped(times(1)); + // THEN the keyguard should be shown + verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL); + // THEN screen pinning toast should be shown + verify(mStatusBarService).showPinningEnterExitToast(false /* entering */); + } + + @Test + public void testClearLockedTasks() throws Exception { + // GIVEN two task records with whitelisted auth that is in lock task mode + TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + + // WHEN calling stopLockTaskMode on the root task + mLockTaskController.clearLockedTasks("testClearLockedTasks"); + + // THEN the lock task mode should be inactive + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + // THEN the first task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr1)); + // THEN the top task should no longer be locked + assertFalse(mLockTaskController.isTaskLocked(tr2)); + // THEN lock task mode should be finished + verifyLockTaskStopped(times(1)); + } + + @Test + public void testUpdateLockTaskPackages() throws Exception { + String[] whitelist1 = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2}; + String[] whitelist2 = {TEST_PACKAGE_NAME}; + + // No package is whitelisted initially + for (String pkg : whitelist1) { + assertFalse("Package shouldn't be whitelisted: " + pkg, + mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg)); + assertFalse("Package shouldn't be whitelisted for user 0: " + pkg, + mLockTaskController.isPackageWhitelisted(0, pkg)); + } + + // Apply whitelist + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist1); + + // Assert the whitelist is applied to the correct user + for (String pkg : whitelist1) { + assertTrue("Package should be whitelisted: " + pkg, + mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg)); + assertFalse("Package shouldn't be whitelisted for user 0: " + pkg, + mLockTaskController.isPackageWhitelisted(0, pkg)); + } + + // Update whitelist + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist2); + + // Assert the new whitelist is applied + assertTrue("Package should remain whitelisted: " + TEST_PACKAGE_NAME, + mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME)); + assertFalse("Package should no longer be whitelisted: " + TEST_PACKAGE_NAME_2, + mLockTaskController.isPackageWhitelisted(TEST_USER_ID, TEST_PACKAGE_NAME_2)); + } + + @Test + public void testUpdateLockTaskPackages_taskRemoved() throws Exception { + // GIVEN two tasks which are whitelisted initially + TaskRecord tr1 = getTaskRecordForUpdate(TEST_PACKAGE_NAME, true); + TaskRecord tr2 = getTaskRecordForUpdate(TEST_PACKAGE_NAME_2, false); + String[] whitelist = {TEST_PACKAGE_NAME, TEST_PACKAGE_NAME_2}; + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); + + // GIVEN the tasks are launched into LockTask mode + mLockTaskController.startLockTaskMode(tr1, false, TEST_UID); + mLockTaskController.startLockTaskMode(tr2, false, TEST_UID); + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + assertTrue(mLockTaskController.isTaskLocked(tr1)); + assertTrue(mLockTaskController.isTaskLocked(tr2)); + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); + + // WHEN removing one package from whitelist + whitelist = new String[] {TEST_PACKAGE_NAME}; + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); + + // THEN the task running that package should be stopped + verify(tr2).performClearTaskLocked(); + assertFalse(mLockTaskController.isTaskLocked(tr2)); + // THEN the other task should remain locked + assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState()); + assertTrue(mLockTaskController.isTaskLocked(tr1)); + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); + + // WHEN removing the last package from whitelist + whitelist = new String[] {}; + mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist); + + // THEN the last task should be cleared, and the system should quit LockTask mode + verify(tr1).performClearTaskLocked(); + assertFalse(mLockTaskController.isTaskLocked(tr1)); + assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState()); + verifyLockTaskStopped(times(1)); + } + + @Test + public void testUpdateLockTaskFeatures() throws Exception { + // GIVEN a locked task + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN lock task mode should be started with default status bar masks + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); + + // reset invocation counter + reset(mStatusBarService); + + // WHEN home button is enabled for lock task mode + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_HOME); + + // THEN status bar should be updated to reflect this change + int expectedFlags = STATUS_BAR_MASK_LOCKED + & ~DISABLE_HOME; + int expectedFlags2 = DISABLE2_MASK; + verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class), + eq(mContext.getPackageName())); + verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class), + eq(mContext.getPackageName())); + + // reset invocation counter + reset(mStatusBarService); + + // WHEN notifications are enabled for lock task mode + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NOTIFICATIONS); + + // THEN status bar should be updated to reflect this change + expectedFlags = STATUS_BAR_MASK_LOCKED + & ~DISABLE_NOTIFICATION_ICONS + & ~DISABLE_NOTIFICATION_ALERTS; + expectedFlags2 = DISABLE2_MASK + & ~DISABLE2_NOTIFICATION_SHADE; + verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class), + eq(mContext.getPackageName())); + verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class), + eq(mContext.getPackageName())); + } + + @Test + public void testUpdateLockTaskFeatures_differentUser() throws Exception { + // GIVEN a locked task + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN lock task mode should be started with default status bar masks + verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK); + + // reset invocation counter + reset(mStatusBarService); + + // WHEN home button is enabled for lock task mode for another user + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID + 1, LOCK_TASK_FEATURE_HOME); + + // THEN status bar shouldn't change + verify(mStatusBarService, never()).disable(anyInt(), any(IBinder.class), + eq(mContext.getPackageName())); + verify(mStatusBarService, never()).disable2(anyInt(), any(IBinder.class), + eq(mContext.getPackageName())); + } + + @Test + public void testUpdateLockTaskFeatures_keyguard() throws Exception { + // GIVEN a locked task + TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED); + mLockTaskController.startLockTaskMode(tr, false, TEST_UID); + + // THEN keyguard should be disabled + verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); + + // WHEN keyguard is enabled for lock task mode + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD); + + // THEN keyguard should be enabled + verify(mWindowManager).reenableKeyguard(any(IBinder.class)); + + // WHEN keyguard is disabled again for lock task mode + mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE); + + // THEN keyguard should be disabled + verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString()); + } + + @Test + public void testGetStatusBarDisableFlags() { + // Note that we don't enumerate all StatusBarManager flags, but only choose a subset to test + + // WHEN nothing is enabled + Pair flags = mLockTaskController.getStatusBarDisableFlags( + LOCK_TASK_FEATURE_NONE); + // THEN unsupported feature flags should still be untouched + assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0); + // THEN everything else should be disabled + assertTrue((StatusBarManager.DISABLE_CLOCK & flags.first) != 0); + assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0); + + // WHEN only home button is enabled + flags = mLockTaskController.getStatusBarDisableFlags( + LOCK_TASK_FEATURE_HOME); + // THEN unsupported feature flags should still be untouched + assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0); + // THEN home button should indeed be enabled + assertTrue((StatusBarManager.DISABLE_HOME & flags.first) == 0); + // THEN other feature flags should remain disabled + assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) != 0); + + // WHEN only global actions menu and notifications are enabled + flags = mLockTaskController.getStatusBarDisableFlags( + DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS + | DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS); + // THEN unsupported feature flags should still be untouched + assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0); + // THEN notifications should be enabled + assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ICONS & flags.first) == 0); + assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ALERTS & flags.first) == 0); + assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) == 0); + // THEN global actions should be enabled + assertTrue((StatusBarManager.DISABLE2_GLOBAL_ACTIONS & flags.second) == 0); + // THEN quick settings should still be disabled + assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0); + } + + private TaskRecord getTaskRecord(int lockTaskAuth) { + return getTaskRecord(TEST_PACKAGE_NAME, lockTaskAuth); + } + + private TaskRecord getTaskRecord(String pkg, int lockTaskAuth) { + final Intent intent = new Intent() + .setComponent(ComponentName.createRelative(pkg, TEST_CLASS_NAME)); + return getTaskRecord(intent, lockTaskAuth); + } + + private TaskRecord getTaskRecord(Intent intent, int lockTaskAuth) { + TaskRecord tr = mock(TaskRecord.class); + tr.mLockTaskAuth = lockTaskAuth; + tr.intent = intent; + tr.userId = TEST_USER_ID; + return tr; + } + + /** + * @param isAppAware {@code true} if the app has marked if_whitelisted in its manifest + */ + private TaskRecord getTaskRecordForUpdate(String pkg, boolean isAppAware) { + final int authIfWhitelisted = isAppAware + ? TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE + : TaskRecord.LOCK_TASK_AUTH_WHITELISTED; + TaskRecord tr = getTaskRecord(pkg, authIfWhitelisted); + doAnswer((invocation) -> { + boolean isWhitelisted = + mLockTaskController.isPackageWhitelisted(TEST_USER_ID, pkg); + tr.mLockTaskAuth = isWhitelisted + ? authIfWhitelisted + : TaskRecord.LOCK_TASK_AUTH_PINNABLE; + return null; + }).when(tr).setLockTaskAuth(); + return tr; + } + + private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception { + // THEN the keyguard should have been disabled + verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString()); + // THEN the status bar should have been disabled + verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class), + eq(mContext.getPackageName())); + verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class), + eq(mContext.getPackageName())); + // THEN recents should have been notified + verify(mRecentTasks).onLockTaskModeStateChanged(anyInt(), eq(TEST_USER_ID)); + // THEN the DO/PO should be informed about the operation + verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME, + TEST_USER_ID); + } + + private void verifyLockTaskStopped(VerificationMode mode) throws Exception { + // THEN the keyguard should have been disabled + verify(mWindowManager, mode).reenableKeyguard(any(IBinder.class)); + // THEN the status bar should have been disabled + verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE), + any(IBinder.class), eq(mContext.getPackageName())); + verify(mStatusBarService, mode).disable2(eq(StatusBarManager.DISABLE2_NONE), + any(IBinder.class), eq(mContext.getPackageName())); + // THEN the DO/PO should be informed about the operation + verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID); + } + + /** + * Special handler implementation that executes any message / runnable posted immediately on the + * thread that it's posted on rather than enqueuing them on its looper. + */ + private static class ImmediatelyExecuteHandler extends Handler { + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + if (msg.getCallback() != null) { + msg.getCallback().run(); + } + return true; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/wmtests/src/com/android/server/am/MemoryStatUtilTest.java new file mode 100644 index 000000000000..18b0988f9726 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/MemoryStatUtilTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static com.android.server.am.MemoryStatUtil.MemoryStat; +import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg; +import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link MemoryStatUtil}. + * + * Build/Install/Run: + * atest WmTests:MemoryStatUtilTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MemoryStatUtilTest { + private String MEMORY_STAT_CONTENTS = String.join( + "\n", + "cache 96", // keep different from total_cache to catch reading wrong value + "rss 97", // keep different from total_rss to catch reading wrong value + "rss_huge 0", + "mapped_file 524288", + "writeback 0", + "swap 95", // keep different from total_rss to catch reading wrong value + "pgpgin 16717", + "pgpgout 5037", + "pgfault 99", // keep different from total_pgfault to catch reading wrong value + "pgmajfault 98", // keep different from total_pgmajfault to catch reading wrong value + "inactive_anon 503808", + "active_anon 46309376", + "inactive_file 876544", + "active_file 81920", + "unevictable 0", + "hierarchical_memory_limit 18446744073709551615", + "hierarchical_memsw_limit 18446744073709551615", + "total_cache 4", + "total_rss 3", + "total_rss_huge 0", + "total_mapped_file 524288", + "total_writeback 0", + "total_swap 5", + "total_pgpgin 16717", + "total_pgpgout 5037", + "total_pgfault 1", + "total_pgmajfault 2", + "total_inactive_anon 503808", + "total_active_anon 46309376", + "total_inactive_file 876544", + "total_active_file 81920", + "total_unevictable 0"); + + private String PROC_STAT_CONTENTS = String.join( + " ", + "1040", + "(system_server)", + "S", + "544", + "544", + "0", + "0", + "-1", + "1077936448", + "1", // this is pgfault + "0", + "2", // this is pgmajfault + "0", + "44533", + "13471", + "0", + "0", + "18", + "-2", + "117", + "0", + "2206", + "1257177088", + "3", // this is rss in bytes + "4294967295", + "2936971264", + "2936991289", + "3198888320", + "3198879848", + "2903927664", + "0", + "4612", + "0", + "1073775864", + "4294967295", + "0", + "0", + "17", + "0", + "0", + "0", + "0", + "0", + "0", + "2936999088", + "2936999936", + "2958692352", + "3198888595", + "3198888671", + "3198888671", + "3198889956", + "0"); + + @Test + public void testParseMemoryStatFromMemcg_parsesCorrectValues() throws Exception { + MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS); + assertEquals(stat.pgfault, 1); + assertEquals(stat.pgmajfault, 2); + assertEquals(stat.rssInBytes, 3); + assertEquals(stat.cacheInBytes, 4); + assertEquals(stat.swapInBytes, 5); + } + + @Test + public void testParseMemoryStatFromMemcg_emptyMemoryStatContents() throws Exception { + MemoryStat stat = parseMemoryStatFromMemcg(""); + assertNull(stat); + + stat = parseMemoryStatFromMemcg(null); + assertNull(stat); + } + + @Test + public void testParseMemoryStatFromProcfs_parsesCorrectValues() throws Exception { + MemoryStat stat = parseMemoryStatFromProcfs(PROC_STAT_CONTENTS); + assertEquals(1, stat.pgfault); + assertEquals(2, stat.pgmajfault); + assertEquals(3, stat.rssInBytes); + assertEquals(0, stat.cacheInBytes); + assertEquals(0, stat.swapInBytes); + } + + @Test + public void testParseMemoryStatFromProcfs_emptyContents() throws Exception { + MemoryStat stat = parseMemoryStatFromProcfs(""); + assertNull(stat); + + stat = parseMemoryStatFromProcfs(null); + assertNull(stat); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java b/services/tests/wmtests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java new file mode 100644 index 000000000000..f812ee6f1fda --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.app.ActivityOptions; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.RemoteAnimationAdapter; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link PendingRemoteAnimationRegistry}. + * + * Build/Install/Run: + * atest WmTests:PendingRemoteAnimationRegistryTest + */ +@SmallTest +@Presubmit +@FlakyTest +@RunWith(AndroidJUnit4.class) +public class PendingRemoteAnimationRegistryTest extends ActivityTestsBase { + + @Mock RemoteAnimationAdapter mAdapter; + private PendingRemoteAnimationRegistry mRegistry; + private final OffsettableClock mClock = new OffsettableClock.Stopped(); + private TestHandler mHandler; + private ActivityManagerService mService; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mService = createActivityManagerService(); + mService.mHandlerThread.getThreadHandler().runWithScissors(() -> { + mHandler = new TestHandler(null, mClock); + }, 0); + mRegistry = new PendingRemoteAnimationRegistry(mService, mHandler); + } + + @Test + public void testOverrideActivityOptions() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + ActivityOptions opts = ActivityOptions.makeBasic(); + opts = mRegistry.overrideOptionsIfNeeded("com.android.test", opts); + assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); + } + + @Test + public void testOverrideActivityOptions_null() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null); + assertNotNull(opts); + assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); + } + + @Test + public void testTimeout() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + mClock.fastForward(5000); + mHandler.timeAdvance(); + assertNull(mRegistry.overrideOptionsIfNeeded("com.android.test", null)); + } + + @Test + public void testTimeout_overridenEntry() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + mClock.fastForward(2500); + mHandler.timeAdvance(); + mRegistry.addPendingAnimation("com.android.test", mAdapter); + mClock.fastForward(1000); + mHandler.timeAdvance(); + final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null); + assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/wmtests/src/com/android/server/am/PersistentConnectionTest.java new file mode 100644 index 000000000000..5c42081d4655 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/PersistentConnectionTest.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.admin.IDeviceAdminService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; + +import org.mockito.ArgumentMatchers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +/** + * Tests for {@link PersistentConnection}. + * + * Build/Install/Run: + * atest WmTests:PersistentConnectionTest + */ +@SmallTest +public class PersistentConnectionTest extends AndroidTestCase { + private static class MyConnection extends PersistentConnection { + public long uptimeMillis = 12345; + + public ArrayList> scheduledRunnables = new ArrayList<>(); + + public MyConnection(String tag, Context context, Handler handler, int userId, + ComponentName componentName, long rebindBackoffSeconds, + double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { + super(tag, context, handler, userId, componentName, + rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds); + } + + @Override + protected IDeviceAdminService asInterface(IBinder binder) { + return (IDeviceAdminService) binder; + } + + @Override + long injectUptimeMillis() { + return uptimeMillis; + } + + @Override + void injectPostAtTime(Runnable r, long uptimeMillis) { + scheduledRunnables.add(Pair.create(r, uptimeMillis)); + } + + @Override + void injectRemoveCallbacks(Runnable r) { + for (int i = scheduledRunnables.size() - 1; i >= 0; i--) { + if (scheduledRunnables.get(i).first.equals(r)) { + scheduledRunnables.remove(i); + } + } + } + + void elapse(long milliSeconds) { + uptimeMillis += milliSeconds; + + // Fire the scheduled runnables. + + // Note we collect first and then run all, because sometimes a scheduled runnable + // calls removeCallbacks. + final ArrayList list = new ArrayList<>(); + + for (int i = scheduledRunnables.size() - 1; i >= 0; i--) { + if (scheduledRunnables.get(i).second <= uptimeMillis) { + list.add(scheduledRunnables.get(i).first); + scheduledRunnables.remove(i); + } + } + + Collections.reverse(list); + for (Runnable r : list) { + r.run(); + } + } + } + + public void testAll() { + final Context context = mock(Context.class); + final int userId = 11; + final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); + final Handler handler = new Handler(Looper.getMainLooper()); + + final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, + /* rebindBackoffSeconds= */ 5, + /* rebindBackoffIncrease= */ 1.5, + /* rebindMaxBackoffSeconds= */ 11); + + assertFalse(conn.isBound()); + assertFalse(conn.isConnected()); + assertFalse(conn.isRebindScheduled()); + assertEquals(5000, conn.getNextBackoffMsForTest()); + assertNull(conn.getServiceBinder()); + + when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), + any(Handler.class), any(UserHandle.class))) + .thenReturn(true); + + // Call bind. + conn.bind(); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertFalse(conn.isRebindScheduled()); + assertNull(conn.getServiceBinder()); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + + verify(context).bindServiceAsUser( + ArgumentMatchers.argThat(intent -> cn.equals(intent.getComponent())), + eq(conn.getServiceConnectionForTest()), + eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), + eq(handler), eq(UserHandle.of(userId))); + + // AM responds... + conn.getServiceConnectionForTest().onServiceConnected(cn, + new IDeviceAdminService.Stub() {}); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertTrue(conn.isConnected()); + assertNotNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + + + // Now connected. + + // Call unbind... + conn.unbind(); + assertFalse(conn.isBound()); + assertFalse(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + // Caller bind again... + conn.bind(); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertFalse(conn.isRebindScheduled()); + assertNull(conn.getServiceBinder()); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + + + // Now connected again. + + // The service got killed... + conn.getServiceConnectionForTest().onServiceDisconnected(cn); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + + // Connected again... + conn.getServiceConnectionForTest().onServiceConnected(cn, + new IDeviceAdminService.Stub() {}); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertTrue(conn.isConnected()); + assertNotNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + + + // Then the binding is "died"... + conn.getServiceConnectionForTest().onBindingDied(cn); + + assertFalse(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertTrue(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + assertEquals( + Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), + conn.uptimeMillis + 5000)), + conn.scheduledRunnables); + + // 5000 ms later... + conn.elapse(5000); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + // Connected. + conn.getServiceConnectionForTest().onServiceConnected(cn, + new IDeviceAdminService.Stub() {}); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertTrue(conn.isConnected()); + assertNotNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + // Then the binding is "died"... + conn.getServiceConnectionForTest().onBindingDied(cn); + + assertFalse(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertTrue(conn.isRebindScheduled()); + + assertEquals(11000, conn.getNextBackoffMsForTest()); + + assertEquals( + Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), + conn.uptimeMillis + 7500)), + conn.scheduledRunnables); + + // Later... + conn.elapse(7500); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + assertEquals(11000, conn.getNextBackoffMsForTest()); + + + // Then the binding is "died"... + conn.getServiceConnectionForTest().onBindingDied(cn); + + assertFalse(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertTrue(conn.isRebindScheduled()); + + assertEquals(11000, conn.getNextBackoffMsForTest()); + + assertEquals( + Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), + conn.uptimeMillis + 11000)), + conn.scheduledRunnables); + + // Call unbind... + conn.unbind(); + assertFalse(conn.isBound()); + assertFalse(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertNull(conn.getServiceBinder()); + assertFalse(conn.isRebindScheduled()); + + // Call bind again... And now the backoff is reset to 5000. + conn.bind(); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isConnected()); + assertFalse(conn.isRebindScheduled()); + assertNull(conn.getServiceBinder()); + + assertEquals(5000, conn.getNextBackoffMsForTest()); + } + + public void testReconnectFiresAfterUnbind() { + final Context context = mock(Context.class); + final int userId = 11; + final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); + final Handler handler = new Handler(Looper.getMainLooper()); + + final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, + /* rebindBackoffSeconds= */ 5, + /* rebindBackoffIncrease= */ 1.5, + /* rebindMaxBackoffSeconds= */ 11); + + when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), + any(Handler.class), any(UserHandle.class))) + .thenReturn(true); + + // Bind. + conn.bind(); + + assertTrue(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertFalse(conn.isRebindScheduled()); + + conn.elapse(1000); + + // Service crashes. + conn.getServiceConnectionForTest().onBindingDied(cn); + + assertFalse(conn.isBound()); + assertTrue(conn.shouldBeBoundForTest()); + assertTrue(conn.isRebindScheduled()); + + assertEquals(7500, conn.getNextBackoffMsForTest()); + + // Call unbind. + conn.unbind(); + assertFalse(conn.isBound()); + assertFalse(conn.shouldBeBoundForTest()); + + // Now, at this point, it's possible that the scheduled runnable had already been fired + // before during the unbind() call, and waiting on mLock. + // To simulate it, we just call the runnable here. + conn.getBindForBackoffRunnableForTest().run(); + + // Should still not be bound. + assertFalse(conn.isBound()); + assertFalse(conn.shouldBeBoundForTest()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/am/RecentTasksTest.java new file mode 100644 index 000000000000..739467a48d16 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/RecentTasksTest.java @@ -0,0 +1,1024 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; + +import static java.lang.Integer.MAX_VALUE; + +import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Looper; +import android.os.RemoteException; +import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.MutableLong; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import com.android.server.am.RecentTasks.Callbacks; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * Tests for {@link RecentTasks}. + * + * Build/Install/Run: + * atest WmTests:RecentTasksTest + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RecentTasksTest extends ActivityTestsBase { + private static final int TEST_USER_0_ID = 0; + private static final int TEST_USER_1_ID = 10; + private static final int TEST_QUIET_USER_ID = 20; + private static final UserInfo DEFAULT_USER_INFO = new UserInfo(); + private static final UserInfo QUIET_USER_INFO = new UserInfo(); + private static int LAST_TASK_ID = 1; + private static int LAST_STACK_ID = 1; + private static int INVALID_STACK_ID = 999; + + private Context mContext = InstrumentationRegistry.getContext(); + private ActivityManagerService mService; + private ActivityDisplay mDisplay; + private ActivityDisplay mOtherDisplay; + private ActivityStack mStack; + private ActivityStack mHomeStack; + private TestTaskPersister mTaskPersister; + private TestRecentTasks mRecentTasks; + private TestRunningTasks mRunningTasks; + + private ArrayList mTasks; + private ArrayList mSameDocumentTasks; + + private CallbacksRecorder mCallbacksRecorder; + + class TestUserController extends UserController { + TestUserController(ActivityManagerService service) { + super(service); + } + + @Override + int[] getCurrentProfileIds() { + return new int[] { TEST_USER_0_ID, TEST_QUIET_USER_ID }; + } + + @Override + Set getProfileIds(int userId) { + Set profileIds = new HashSet<>(); + profileIds.add(TEST_USER_0_ID); + profileIds.add(TEST_QUIET_USER_ID); + return profileIds; + } + + @Override + UserInfo getUserInfo(int userId) { + switch (userId) { + case TEST_USER_0_ID: + case TEST_USER_1_ID: + return DEFAULT_USER_INFO; + case TEST_QUIET_USER_ID: + return QUIET_USER_INFO; + } + return null; + } + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); + mService = setupActivityManagerService(new MyTestActivityManagerService(mContext)); + mRecentTasks = (TestRecentTasks) mService.getRecentTasks(); + mRecentTasks.loadParametersFromResources(mContext.getResources()); + mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); + mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack); + mCallbacksRecorder = new CallbacksRecorder(); + mRecentTasks.registerCallback(mCallbacksRecorder); + QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE; + + mTasks = new ArrayList<>(); + mTasks.add(createTaskBuilder(".Task1").build()); + mTasks.add(createTaskBuilder(".Task2").build()); + mTasks.add(createTaskBuilder(".Task3").build()); + mTasks.add(createTaskBuilder(".Task4").build()); + mTasks.add(createTaskBuilder(".Task5").build()); + + mSameDocumentTasks = new ArrayList<>(); + mSameDocumentTasks.add(createDocumentTask(".DocumentTask1")); + mSameDocumentTasks.add(createDocumentTask(".DocumentTask1")); + } + + @Test + public void testCallbacks() throws Exception { + // Add some tasks + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.add(mTasks.get(1)); + assertTrue(mCallbacksRecorder.added.contains(mTasks.get(0)) + && mCallbacksRecorder.added.contains(mTasks.get(1))); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + mCallbacksRecorder.clear(); + + // Remove some tasks + mRecentTasks.remove(mTasks.get(0)); + mRecentTasks.remove(mTasks.get(1)); + assertTrue(mCallbacksRecorder.added.isEmpty()); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(0))); + assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(1))); + mCallbacksRecorder.clear(); + + // Remove the callback, ensure we don't get any calls + mRecentTasks.unregisterCallback(mCallbacksRecorder); + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.remove(mTasks.get(0)); + assertTrue(mCallbacksRecorder.added.isEmpty()); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + } + + @Test + public void testAddTasksNoMultiple_expectNoTrim() throws Exception { + // Add same non-multiple-task document tasks will remove the task (to re-add it) but not + // trim it + TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); + TaskRecord documentTask2 = createDocumentTask(".DocumentTask1"); + mRecentTasks.add(documentTask1); + mRecentTasks.add(documentTask2); + assertTrue(mCallbacksRecorder.added.contains(documentTask1)); + assertTrue(mCallbacksRecorder.added.contains(documentTask2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.contains(documentTask1)); + } + + @Test + public void testAddTasksMaxTaskRecents_expectNoTrim() throws Exception { + // Add a task hitting max-recents for that app will remove the task (to add the next one) + // but not trim it + TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); + TaskRecord documentTask2 = createDocumentTask(".DocumentTask1"); + documentTask1.maxRecents = 1; + documentTask2.maxRecents = 1; + mRecentTasks.add(documentTask1); + mRecentTasks.add(documentTask2); + assertTrue(mCallbacksRecorder.added.contains(documentTask1)); + assertTrue(mCallbacksRecorder.added.contains(documentTask2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.contains(documentTask1)); + } + + @Test + public void testAddTasksSameTask_expectNoTrim() throws Exception { + // Add a task that is already in the task list does not trigger any callbacks, it just + // moves in the list + TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); + mRecentTasks.add(documentTask1); + mRecentTasks.add(documentTask1); + assertTrue(mCallbacksRecorder.added.size() == 1); + assertTrue(mCallbacksRecorder.added.contains(documentTask1)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + } + + @Test + public void testAddTasksMultipleDocumentTasks_expectNoTrim() throws Exception { + // Add same multiple-task document tasks does not trim the first tasks + TaskRecord documentTask1 = createDocumentTask(".DocumentTask1", + FLAG_ACTIVITY_MULTIPLE_TASK); + TaskRecord documentTask2 = createDocumentTask(".DocumentTask1", + FLAG_ACTIVITY_MULTIPLE_TASK); + mRecentTasks.add(documentTask1); + mRecentTasks.add(documentTask2); + assertTrue(mCallbacksRecorder.added.size() == 2); + assertTrue(mCallbacksRecorder.added.contains(documentTask1)); + assertTrue(mCallbacksRecorder.added.contains(documentTask2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + } + + @Test + public void testAddTasksMultipleTasks_expectRemovedNoTrim() throws Exception { + // Add multiple same-affinity non-document tasks, ensure that it removes the other task, + // but that it does not trim it + TaskRecord task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .build(); + TaskRecord task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .build(); + mRecentTasks.add(task1); + assertTrue(mCallbacksRecorder.added.size() == 1); + assertTrue(mCallbacksRecorder.added.contains(task1)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + mCallbacksRecorder.clear(); + mRecentTasks.add(task2); + assertTrue(mCallbacksRecorder.added.size() == 1); + assertTrue(mCallbacksRecorder.added.contains(task2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.size() == 1); + assertTrue(mCallbacksRecorder.removed.contains(task1)); + } + + @Test + public void testAddTasksDifferentStacks_expectNoRemove() throws Exception { + // Adding the same task with different activity types should not trigger removal of the + // other task + TaskRecord task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .setStack(mHomeStack).build(); + TaskRecord task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK) + .setStack(mStack).build(); + mRecentTasks.add(task1); + mRecentTasks.add(task2); + assertTrue(mCallbacksRecorder.added.size() == 2); + assertTrue(mCallbacksRecorder.added.contains(task1)); + assertTrue(mCallbacksRecorder.added.contains(task2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + } + + @Test + public void testAddTaskCompatibleActivityType_expectRemove() throws Exception { + // Test with undefined activity type since the type is not persisted by the task persister + // and we want to ensure that a new task will match a restored task + Configuration config1 = new Configuration(); + config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); + TaskRecord task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .build(); + task1.onConfigurationChanged(config1); + assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED); + mRecentTasks.add(task1); + mCallbacksRecorder.clear(); + + TaskRecord task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .build(); + assertTrue(task2.getActivityType() == ACTIVITY_TYPE_STANDARD); + mRecentTasks.add(task2); + assertTrue(mCallbacksRecorder.added.size() == 1); + assertTrue(mCallbacksRecorder.added.contains(task2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.size() == 1); + assertTrue(mCallbacksRecorder.removed.contains(task1)); + } + + @Test + public void testAddTaskCompatibleActivityTypeDifferentUser_expectNoRemove() throws Exception { + Configuration config1 = new Configuration(); + config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); + TaskRecord task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .setUserId(TEST_USER_0_ID) + .build(); + task1.onConfigurationChanged(config1); + assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED); + mRecentTasks.add(task1); + mCallbacksRecorder.clear(); + + TaskRecord task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .setUserId(TEST_USER_1_ID) + .build(); + assertTrue(task2.getActivityType() == ACTIVITY_TYPE_STANDARD); + mRecentTasks.add(task2); + assertTrue(mCallbacksRecorder.added.size() == 1); + assertTrue(mCallbacksRecorder.added.contains(task2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + } + + @Test + public void testAddTaskCompatibleWindowingMode_expectRemove() throws Exception { + Configuration config1 = new Configuration(); + config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED); + TaskRecord task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .build(); + task1.onConfigurationChanged(config1); + assertTrue(task1.getWindowingMode() == WINDOWING_MODE_UNDEFINED); + mRecentTasks.add(task1); + mCallbacksRecorder.clear(); + + Configuration config2 = new Configuration(); + config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + TaskRecord task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .build(); + task2.onConfigurationChanged(config2); + assertTrue(task2.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + mRecentTasks.add(task2); + + assertTrue(mCallbacksRecorder.added.size() == 1); + assertTrue(mCallbacksRecorder.added.contains(task2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.size() == 1); + assertTrue(mCallbacksRecorder.removed.contains(task1)); + } + + @Test + public void testAddTaskIncompatibleWindowingMode_expectNoRemove() throws Exception { + Configuration config1 = new Configuration(); + config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + TaskRecord task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .build(); + task1.onConfigurationChanged(config1); + assertTrue(task1.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + mRecentTasks.add(task1); + + Configuration config2 = new Configuration(); + config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); + TaskRecord task2 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .setStack(mStack) + .build(); + task2.onConfigurationChanged(config2); + assertTrue(task2.getWindowingMode() == WINDOWING_MODE_PINNED); + mRecentTasks.add(task2); + + assertTrue(mCallbacksRecorder.added.size() == 2); + assertTrue(mCallbacksRecorder.added.contains(task1)); + assertTrue(mCallbacksRecorder.added.contains(task2)); + assertTrue(mCallbacksRecorder.trimmed.isEmpty()); + assertTrue(mCallbacksRecorder.removed.isEmpty()); + } + + @Test + public void testUsersTasks() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + + // Setup some tasks for the users + mTaskPersister.userTaskIdsOverride = new SparseBooleanArray(); + mTaskPersister.userTaskIdsOverride.put(1, true); + mTaskPersister.userTaskIdsOverride.put(2, true); + mTaskPersister.userTasksOverride = new ArrayList<>(); + mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build()); + mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build()); + + // Assert no user tasks are initially loaded + assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0); + + // Load user 0 tasks + mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); + assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); + assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); + assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); + + // Load user 1 tasks + mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID); + assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); + assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID)); + assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); + assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); + assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_1_ID)); + assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_1_ID)); + + // Unload user 1 tasks + mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_1_ID); + assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); + assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID)); + assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); + assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); + + // Unload user 0 tasks + mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID); + assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID)); + assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID)); + } + + @Test + public void testOrderedIteration() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + TaskRecord task1 = createTaskBuilder(".Task1").build(); + task1.lastActiveTime = new Random().nextInt(); + TaskRecord task2 = createTaskBuilder(".Task1").build(); + task2.lastActiveTime = new Random().nextInt(); + TaskRecord task3 = createTaskBuilder(".Task1").build(); + task3.lastActiveTime = new Random().nextInt(); + TaskRecord task4 = createTaskBuilder(".Task1").build(); + task4.lastActiveTime = new Random().nextInt(); + mRecentTasks.add(task1); + mRecentTasks.add(task2); + mRecentTasks.add(task3); + mRecentTasks.add(task4); + + MutableLong prevLastActiveTime = new MutableLong(0); + final ArrayList tasks = mRecentTasks.getRawTasks(); + for (int i = 0; i < tasks.size(); i++) { + final TaskRecord task = tasks.get(i); + assertTrue(task.lastActiveTime >= prevLastActiveTime.value); + prevLastActiveTime.value = task.lastActiveTime; + } + } + + @Test + public void testTrimToGlobalMaxNumRecents() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + + // Limit the global maximum number of recent tasks to a fixed size + mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */); + + // Add N+1 tasks + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.add(mTasks.get(1)); + mRecentTasks.add(mTasks.get(2)); + + // Ensure that the last task was trimmed as an inactive task + assertTrimmed(mTasks.get(0)); + } + + @Test + public void testTrimQuietProfileTasks() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build(); + TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build(); + mRecentTasks.add(qt1); + mRecentTasks.add(qt2); + + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.add(mTasks.get(1)); + + // Ensure that the quiet user's tasks was trimmed once the new tasks were added + assertTrimmed(qt1, qt2); + } + + @Test + public void testSessionDuration() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */); + + TaskRecord t1 = createTaskBuilder(".Task1").build(); + t1.touchActiveTime(); + mRecentTasks.add(t1); + + // Force a small sleep just beyond the session duration + SystemClock.sleep(75); + + TaskRecord t2 = createTaskBuilder(".Task2").build(); + t2.touchActiveTime(); + mRecentTasks.add(t2); + + // Assert that the old task has been removed due to being out of the active session + assertTrimmed(t1); + } + + @Test + public void testVisibleTasks_excludedFromRecents() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); + + TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .build(); + TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .build(); + + mRecentTasks.add(excludedTask1); + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.add(mTasks.get(1)); + mRecentTasks.add(mTasks.get(2)); + mRecentTasks.add(excludedTask2); + + // The last excluded task should be trimmed, while the first-most excluded task should not + assertTrimmed(excludedTask1); + } + + @Test + public void testVisibleTasks_minNum() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */); + + for (int i = 0; i < 4; i++) { + final TaskRecord task = mTasks.get(i); + task.touchActiveTime(); + mRecentTasks.add(task); + } + + // Force a small sleep just beyond the session duration + SystemClock.sleep(50); + + // Add a new task to trigger tasks to be trimmed + mRecentTasks.add(mTasks.get(4)); + + // Ensure that there are a minimum number of tasks regardless of session length + assertNoTasksTrimmed(); + } + + @Test + public void testVisibleTasks_maxNum() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */); + + for (int i = 0; i < 5; i++) { + final TaskRecord task = mTasks.get(i); + task.touchActiveTime(); + mRecentTasks.add(task); + } + + // Ensure that only the last number of max tasks are kept + assertTrimmed(mTasks.get(0), mTasks.get(1)); + } + + @Test + public void testBackStackTasks_expectNoTrim() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); + + final MyTestActivityStackSupervisor supervisor = + (MyTestActivityStackSupervisor) mService.mStackSupervisor; + final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); + supervisor.setHomeStack(homeStack); + + // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all + // the tasks belong in stacks above the home stack + mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build()); + + assertNoTasksTrimmed(); + } + + @Test + public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); + + final MyTestActivityStackSupervisor supervisor = + (MyTestActivityStackSupervisor) mService.mStackSupervisor; + final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); + supervisor.setHomeStack(homeStack); + + // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind + // the home stack is trimmed once a new task is added + final TaskRecord behindHomeTask = createTaskBuilder(".Task1") + .setStack(behindHomeStack) + .build(); + mRecentTasks.add(behindHomeTask); + mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build()); + + assertTrimmed(behindHomeTask); + } + + @Test + public void testOtherDisplayTasks_expectNoTrim() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); + + final MyTestActivityStackSupervisor supervisor = + (MyTestActivityStackSupervisor) mService.mStackSupervisor; + final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor); + supervisor.setHomeStack(homeStack); + + // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not + // removed + mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build()); + mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build()); + + assertNoTasksTrimmed(); + } + + @Test + public void testRemovePackageByName() throws Exception { + // Add a number of tasks with the same package name + mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".Task1").build()); + mRecentTasks.add(createTaskBuilder("com.android.pkg2", ".Task2").build()); + mRecentTasks.add(createTaskBuilder("com.android.pkg3", ".Task3").build()); + mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".Task4").build()); + mRecentTasks.removeTasksByPackageName("com.android.pkg1", TEST_USER_0_ID); + + final ArrayList tasks = mRecentTasks.getRawTasks(); + for (int i = 0; i < tasks.size(); i++) { + if (tasks.get(i).intent.getComponent().getPackageName().equals("com.android.pkg1")) { + fail("Expected com.android.pkg1 tasks to be removed"); + } + } + } + + @Test + public void testNotRecentsComponent_denyApiAccess() throws Exception { + doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(), + anyInt(), anyInt()); + + // Expect the following methods to fail due to recents component not being set + mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION); + testRecentTasksApis(false /* expectNoSecurityException */); + // Don't throw for the following tests + mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY); + testGetTasksApis(false /* expectNoSecurityException */); + } + + @Test + public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception { + doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(), + anyInt(), anyInt()); + + // Set the recents component and ensure that the following calls do not fail + mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT); + testRecentTasksApis(true /* expectNoSecurityException */); + testGetTasksApis(true /* expectNoSecurityException */); + } + + private void testRecentTasksApis(boolean expectCallable) { + assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID)); + assertSecurityException(expectCallable, + () -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED})); + assertSecurityException(expectCallable, + () -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED})); + assertSecurityException(expectCallable, () -> mService.removeTask(0)); + assertSecurityException(expectCallable, + () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true)); + assertSecurityException(expectCallable, + () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true)); + assertSecurityException(expectCallable, + () -> mService.setTaskWindowingModeSplitScreenPrimary(0, + SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true)); + assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true)); + assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0)); + assertSecurityException(expectCallable, + () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect())); + assertSecurityException(expectCallable, + () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0)); + assertSecurityException(expectCallable, + () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(), + new Rect())); + assertSecurityException(expectCallable, + () -> mService.resizePinnedStack(new Rect(), new Rect())); + assertSecurityException(expectCallable, () -> mService.getAllStackInfos()); + assertSecurityException(expectCallable, + () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED)); + assertSecurityException(expectCallable, () -> { + try { + mService.getFocusedStackInfo(); + } catch (RemoteException e) { + // Ignore + } + }); + assertSecurityException(expectCallable, + () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); + assertSecurityException(expectCallable, + () -> mService.startActivityFromRecents(0, new Bundle())); + assertSecurityException(expectCallable, + () -> mService.getTaskSnapshot(0, true)); + assertSecurityException(expectCallable, () -> { + try { + mService.registerTaskStackListener(null); + } catch (RemoteException e) { + // Ignore + } + }); + assertSecurityException(expectCallable, () -> { + try { + mService.unregisterTaskStackListener(null); + } catch (RemoteException e) { + // Ignore + } + }); + assertSecurityException(expectCallable, () -> mService.getTaskDescription(0)); + assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0)); + assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, + null)); + assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation(true)); + assertSecurityException(expectCallable, () -> mService.stopAppSwitches()); + assertSecurityException(expectCallable, () -> mService.resumeAppSwitches()); + } + + private void testGetTasksApis(boolean expectCallable) { + mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID); + mService.getTasks(MAX_VALUE); + if (expectCallable) { + assertTrue(mRecentTasks.lastAllowed); + assertTrue(mRunningTasks.lastAllowed); + } else { + assertFalse(mRecentTasks.lastAllowed); + assertFalse(mRunningTasks.lastAllowed); + } + } + + private TaskBuilder createTaskBuilder(String className) { + return createTaskBuilder(mContext.getPackageName(), className); + } + + private TaskBuilder createTaskBuilder(String packageName, String className) { + return new TaskBuilder(mService.mStackSupervisor) + .setComponent(new ComponentName(packageName, className)) + .setStack(mStack) + .setTaskId(LAST_TASK_ID++) + .setUserId(TEST_USER_0_ID); + } + + private TaskRecord createDocumentTask(String className) { + return createDocumentTask(className, 0); + } + + private TaskRecord createDocumentTask(String className, int flags) { + TaskRecord task = createTaskBuilder(className) + .setFlags(FLAG_ACTIVITY_NEW_DOCUMENT | flags) + .build(); + task.affinity = null; + task.maxRecents = ActivityManager.getMaxAppRecentsLimitStatic(); + return task; + } + + private boolean arrayContainsUser(int[] userIds, int targetUserId) { + Arrays.sort(userIds); + return Arrays.binarySearch(userIds, targetUserId) >= 0; + } + + private void assertNoTasksTrimmed() { + assertTrimmed(); + } + + private void assertTrimmed(TaskRecord... tasks) { + final ArrayList trimmed = mCallbacksRecorder.trimmed; + final ArrayList removed = mCallbacksRecorder.removed; + assertTrue("Expected " + tasks.length + " trimmed tasks, got " + trimmed.size(), + trimmed.size() == tasks.length); + assertTrue("Expected " + tasks.length + " removed tasks, got " + removed.size(), + removed.size() == tasks.length); + for (TaskRecord task : tasks) { + assertTrue("Expected trimmed task: " + task, trimmed.contains(task)); + assertTrue("Expected removed task: " + task, removed.contains(task)); + } + } + + private void assertSecurityException(boolean expectCallable, Runnable runnable) { + boolean noSecurityException = true; + try { + runnable.run(); + } catch (SecurityException se) { + noSecurityException = false; + } catch (Exception e) { + // We only care about SecurityExceptions, fall through here + e.printStackTrace(); + } + if (noSecurityException != expectCallable) { + fail("Expected callable: " + expectCallable + " but got no security exception: " + + noSecurityException); + } + } + + private class MyTestActivityManagerService extends TestActivityManagerService { + MyTestActivityManagerService(Context context) { + super(context); + } + + @Override + protected ActivityStackSupervisor createTestSupervisor() { + return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper()); + } + + @Override + protected RecentTasks createRecentTasks() { + return new TestRecentTasks(this, mTaskPersister, new TestUserController(this)); + } + + @Override + public boolean isUserRunning(int userId, int flags) { + return true; + } + } + + private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor { + public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) { + super(service, looper); + } + + @Override + public void initialize() { + super.initialize(); + mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); + mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY); + attachDisplay(mOtherDisplay); + attachDisplay(mDisplay); + } + + @Override + RunningTasks createRunningTasks() { + mRunningTasks = new TestRunningTasks(); + return mRunningTasks; + } + + void setHomeStack(ActivityStack stack) { + mHomeStack = stack; + } + } + + private class MyTestActivityStack extends TestActivityStack { + private ActivityDisplay mDisplay = null; + + MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) { + super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true); + mDisplay = display; + } + + @Override + ActivityDisplay getDisplay() { + if (mDisplay != null) { + return mDisplay; + } + return super.getDisplay(); + } + } + + private static class CallbacksRecorder implements Callbacks { + ArrayList added = new ArrayList<>(); + ArrayList trimmed = new ArrayList<>(); + ArrayList removed = new ArrayList<>(); + + void clear() { + added.clear(); + trimmed.clear(); + removed.clear(); + } + + @Override + public void onRecentTaskAdded(TaskRecord task) { + added.add(task); + } + + @Override + public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { + if (wasTrimmed) { + trimmed.add(task); + } + removed.add(task); + } + } + + private static class TestTaskPersister extends TaskPersister { + SparseBooleanArray userTaskIdsOverride; + ArrayList userTasksOverride; + + TestTaskPersister(File workingDir) { + super(workingDir); + } + + @Override + SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { + if (userTaskIdsOverride != null) { + return userTaskIdsOverride; + } + return super.loadPersistedTaskIdsForUser(userId); + } + + @Override + List restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) { + if (userTasksOverride != null) { + return userTasksOverride; + } + return super.restoreTasksForUserLocked(userId, preaddedTasks); + } + } + + private static class TestRecentTasks extends RecentTasks { + static final int GRANT = 0; + static final int DENY = 1; + static final int DENY_THROW_SECURITY_EXCEPTION = 2; + + private boolean mOverrideIsCallerRecents; + private boolean mIsTrimmableOverride; + private int mIsCallerRecentsPolicy; + + boolean lastAllowed; + + TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister, + UserController userController) { + super(service, taskPersister, userController); + } + + @Override + boolean isCallerRecents(int callingUid) { + if (mOverrideIsCallerRecents) { + switch (mIsCallerRecentsPolicy) { + case GRANT: + return true; + case DENY: + return false; + case DENY_THROW_SECURITY_EXCEPTION: + throw new SecurityException(); + } + } + return super.isCallerRecents(callingUid); + } + + void setIsCallerRecentsOverride(int policy) { + mOverrideIsCallerRecents = true; + mIsCallerRecentsPolicy = policy; + } + + /** + * To simplify the setup for some tests, the caller can request that we only rely on the + * visible range test to determine what is trimmable. In this case, we don't try to + * use the stack order to determine additionally if the task is trimmable when it is not + * in the visible range. + */ + void setOnlyTestVisibleRange() { + mIsTrimmableOverride = true; + } + + @Override + ParceledListSlice getRecentTasks(int maxNum, int flags, + boolean getTasksAllowed, + boolean getDetailedTasks, int userId, int callingUid) { + lastAllowed = getTasksAllowed; + return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId, + callingUid); + } + + @Override + protected boolean isTrimmable(TaskRecord task) { + return mIsTrimmableOverride || super.isTrimmable(task); + } + } + + private static class TestRunningTasks extends RunningTasks { + boolean lastAllowed; + + @Override + void getTasks(int maxNum, List list, int ignoreActivityType, + int ignoreWindowingMode, SparseArray activityDisplays, + int callingUid, boolean allowed) { + lastAllowed = allowed; + super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays, + callingUid, allowed); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/am/RecentsAnimationTest.java new file mode 100644 index 000000000000..ee576e310982 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/RecentsAnimationTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.IRecentsAnimationRunner; + +import com.android.server.AttributeCache; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link RecentsAnimation}. + * + * Build/Install/Run: + * atest WmTests:RecentsAnimationTest + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RecentsAnimationTest extends ActivityTestsBase { + private static final int TEST_CALLING_PID = 3; + + private Context mContext = InstrumentationRegistry.getContext(); + private ActivityManagerService mService; + private ComponentName mRecentsComponent; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity"); + mService = setupActivityManagerService(new MyTestActivityManagerService(mContext)); + AttributeCache.init(mContext); + } + + @Test + public void testCancelAnimationOnStackOrderChange() throws Exception { + ActivityStack fullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + ActivityStack recentsStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); + ActivityRecord recentsActivity = new ActivityBuilder(mService) + .setComponent(mRecentsComponent) + .setCreateTask(true) + .setStack(recentsStack) + .build(); + ActivityStack fullscreenStack2 = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + ActivityRecord fsActivity = new ActivityBuilder(mService) + .setComponent(new ComponentName(mContext.getPackageName(), "App1")) + .setCreateTask(true) + .setStack(fullscreenStack2) + .build(); + doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation(); + + // Start the recents animation + Intent recentsIntent = new Intent(); + recentsIntent.setComponent(mRecentsComponent); + mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class)); + + fullscreenStack.moveToFront("Activity start"); + + // Ensure that the recents animation was canceled + verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously( + eq(REORDER_KEEP_IN_PLACE), any()); + } + + private class MyTestActivityManagerService extends TestActivityManagerService { + MyTestActivityManagerService(Context context) { + super(context); + } + + @Override + protected RecentTasks createRecentTasks() { + RecentTasks recents = mock(RecentTasks.class); + doReturn(mRecentsComponent).when(recents).getRecentsComponent(); + System.out.println(mRecentsComponent); + return recents; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/am/RunningTasksTest.java new file mode 100644 index 000000000000..4e5fbd8f8279 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/RunningTasksTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM; + +import static org.junit.Assert.assertTrue; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.ComponentName; +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.SparseArray; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + + /** + * Tests for {@link RunningTasks}. + * + * Build/Install/Run: + * atest WmTests:RunningTasksTest + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RunningTasksTest extends ActivityTestsBase { + + private Context mContext = InstrumentationRegistry.getContext(); + private ActivityManagerService mService; + + private RunningTasks mRunningTasks; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mService = createActivityManagerService(); + mRunningTasks = new RunningTasks(); + } + + @Test + public void testCollectTasksByLastActiveTime() throws Exception { + // Create a number of stacks with tasks (of incrementing active time) + final ActivityStackSupervisor supervisor = mService.mStackSupervisor; + final SparseArray displays = new SparseArray<>(); + final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY); + displays.put(DEFAULT_DISPLAY, display); + + final int numStacks = 2; + for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) { + final ActivityStack stack = new TestActivityStack(display, stackIndex, supervisor, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true); + display.addChild(stack, POSITION_BOTTOM); + } + + final int numTasks = 10; + int activeTime = 0; + for (int i = 0; i < numTasks; i++) { + createTask(display.getChildAt(i % numStacks), ".Task" + i, i, activeTime++); + } + + // Ensure that the latest tasks were returned in order of decreasing last active time, + // collected from all tasks across all the stacks + final int numFetchTasks = 5; + ArrayList tasks = new ArrayList<>(); + mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED, + displays, -1 /* callingUid */, true /* allowed */); + assertTrue(tasks.size() == numFetchTasks); + for (int i = 0; i < numFetchTasks; i++) { + assertTrue(tasks.get(i).id == (numTasks - i - 1)); + } + + // Ensure that requesting more than the total number of tasks only returns the subset + // and does not crash + tasks.clear(); + mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED, + displays, -1 /* callingUid */, true /* allowed */); + assertTrue(tasks.size() == numTasks); + for (int i = 0; i < numTasks; i++) { + assertTrue(tasks.get(i).id == (numTasks - i - 1)); + } + } + + /** + * Create a task with a single activity in it, with the given last active time. + */ + private TaskRecord createTask(ActivityStack stack, String className, int taskId, + int lastActiveTime) { + final TaskRecord task = new TaskBuilder(mService.mStackSupervisor) + .setComponent(new ComponentName(mContext.getPackageName(), className)) + .setTaskId(taskId) + .setStack(stack) + .build(); + task.lastActiveTime = lastActiveTime; + final ActivityRecord activity = new ActivityBuilder(mService) + .setTask(task) + .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity")) + .build(); + return task; + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/am/SafeActivityOptionsTest.java new file mode 100644 index 000000000000..3f05c8c7e9f5 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/SafeActivityOptionsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static org.junit.Assert.assertEquals; + +import android.app.ActivityOptions; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SafeActivityOptions}. + * + * Build/Install/Run: + * atest WmTests:SafeActivityOptionsTest + */ +@MediumTest +@Presubmit +@FlakyTest +@RunWith(AndroidJUnit4.class) +public class SafeActivityOptionsTest { + + @Test + public void testMerge() { + final ActivityOptions opts1 = ActivityOptions.makeBasic(); + opts1.setLaunchDisplayId(5); + final ActivityOptions opts2 = ActivityOptions.makeBasic(); + opts2.setLaunchDisplayId(6); + final SafeActivityOptions options = new SafeActivityOptions(opts1); + final ActivityOptions result = options.mergeActivityOptions(opts1, opts2); + assertEquals(6, result.getLaunchDisplayId()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/am/TaskLaunchParamsModifierTests.java new file mode 100644 index 000000000000..7fb63d3d4355 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/TaskLaunchParamsModifierTests.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; + +import static org.junit.Assert.assertEquals; + +import android.content.pm.ActivityInfo.WindowLayout; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Gravity; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for exercising resizing task bounds. + * + * Build/Install/Run: + * atest WmTests:TaskLaunchParamsModifierTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskLaunchParamsModifierTests extends ActivityTestsBase { + private final static int STACK_WIDTH = 100; + private final static int STACK_HEIGHT = 200; + + private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT); + + private ActivityManagerService mService; + private ActivityStack mStack; + private TaskRecord mTask; + + private TaskLaunchParamsModifier mPositioner; + + private LaunchParamsController.LaunchParams mCurrent; + private LaunchParamsController.LaunchParams mResult; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mService = createActivityManagerService(); + mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */); + mStack.requestResize(STACK_BOUNDS); + + // We must create the task after resizing to make sure it does not inherit the stack + // dimensions on resize. + mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + + mPositioner = new TaskLaunchParamsModifier(); + + mResult = new LaunchParamsController.LaunchParams(); + mCurrent = new LaunchParamsController.LaunchParams(); + } + + /** + * Ensures that the setup bounds are set as expected with the stack bounds set and the task + * bounds still {@code null}. + * @throws Exception + */ + @Test + public void testInitialBounds() throws Exception { + assertEquals(mStack.getOverrideBounds(), STACK_BOUNDS); + assertEquals(mTask.getOverrideBounds(), new Rect()); + } + + /** + * Ensures that a task positioned with no {@link WindowLayout} receives the default launch + * position. + * @throws Exception + */ + @Test + public void testLaunchNoWindowLayout() throws Exception { + assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, null /*layout*/, + null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult)); + assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult.mBounds); + } + + /** + * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch + * position. + * @throws Exception + */ + @Test + public void testlaunchEmptyWindowLayout() throws Exception { + assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, + new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/, + null /*source*/, null /*options*/, mCurrent, mResult)); + assertEquals(mResult.mBounds, getDefaultBounds(Gravity.NO_GRAVITY)); + } + + /** + * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned + * according to specification. + * @throws Exception + */ + @Test + public void testlaunchWindowLayoutGravity() throws Exception { + // Unspecified gravity should be ignored + testGravity(Gravity.NO_GRAVITY); + + // Unsupported gravity should be ignored + testGravity(Gravity.LEFT); + testGravity(Gravity.RIGHT); + + // Test defaults for vertical gravities + testGravity(Gravity.TOP); + testGravity(Gravity.BOTTOM); + + // Test corners + testGravity(Gravity.TOP | Gravity.LEFT); + testGravity(Gravity.TOP | Gravity.RIGHT); + testGravity(Gravity.BOTTOM | Gravity.LEFT); + testGravity(Gravity.BOTTOM | Gravity.RIGHT); + } + + private void testGravity(int gravity) { + try { + assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, + new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/, + null /*source*/, null /*options*/, mCurrent, mResult)); + assertEquals(mResult.mBounds, getDefaultBounds(gravity)); + } finally { + mCurrent.reset(); + mResult.reset(); + } + } + + /** + * Ensures that a task which causes a conflict with another task when positioned is adjusted as + * expected. + * @throws Exception + */ + @Test + public void testLaunchWindowCenterConflict() throws Exception { + testConflict(Gravity.NO_GRAVITY); + testConflict(Gravity.TOP); + testConflict(Gravity.BOTTOM); + testConflict(Gravity.TOP | Gravity.LEFT); + testConflict(Gravity.TOP | Gravity.RIGHT); + testConflict(Gravity.BOTTOM | Gravity.LEFT); + testConflict(Gravity.BOTTOM | Gravity.RIGHT); + } + + private void testConflict(int gravity) { + final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0); + + // layout first task + mService.mStackSupervisor.getLaunchParamsController().layoutTask(mTask, layout); + + // Second task will be laid out on top of the first so starting bounds is the same. + final Rect expectedBounds = new Rect(mTask.getOverrideBounds()); + + ActivityRecord activity = null; + TaskRecord secondTask = null; + + // wrap with try/finally to ensure cleanup of activity/stack. + try { + // empty tasks are ignored in conflicts + activity = new ActivityBuilder(mService).setTask(mTask).build(); + + // Create secondary task + secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); + + // layout second task + assertEquals(RESULT_CONTINUE, + mPositioner.onCalculate(secondTask, layout, null /*activity*/, + null /*source*/, null /*options*/, mCurrent, mResult)); + + if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT) + || (gravity & (Gravity.BOTTOM | Gravity.RIGHT)) + == (Gravity.BOTTOM | Gravity.RIGHT)) { + expectedBounds.offset(-TaskLaunchParamsModifier.getHorizontalStep( + mStack.getOverrideBounds()), 0); + } else if ((gravity & Gravity.TOP) == Gravity.TOP + || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { + expectedBounds.offset( + TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), 0); + } else { + expectedBounds.offset( + TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), + TaskLaunchParamsModifier.getVerticalStep(mStack.getOverrideBounds())); + } + + assertEquals(mResult.mBounds, expectedBounds); + } finally { + // Remove task and activity to prevent influencing future tests + if (activity != null) { + mTask.removeActivity(activity); + } + + if (secondTask != null) { + mStack.removeTask(secondTask, "cleanup", ActivityStack.REMOVE_TASK_MODE_DESTROYING); + } + } + } + + private Rect getDefaultBounds(int gravity) { + final Rect bounds = new Rect(); + bounds.set(mStack.getOverrideBounds()); + + final int verticalInset = + TaskLaunchParamsModifier.getFreeformStartTop(mStack.getOverrideBounds()); + final int horizontalInset = + TaskLaunchParamsModifier.getFreeformStartLeft(mStack.getOverrideBounds()); + + bounds.inset(horizontalInset, verticalInset); + + if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)) { + bounds.offsetTo(horizontalInset * 2, 0); + } else if ((gravity & Gravity.TOP) == Gravity.TOP) { + bounds.offsetTo(0, 0); + } else if ((gravity & (Gravity.BOTTOM | Gravity.RIGHT)) + == (Gravity.BOTTOM | Gravity.RIGHT)) { + bounds.offsetTo(horizontalInset * 2, verticalInset * 2); + } else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { + bounds.offsetTo(0, verticalInset * 2); + } + + return bounds; + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/TaskPersisterTest.java b/services/tests/wmtests/src/com/android/server/am/TaskPersisterTest.java new file mode 100644 index 000000000000..cdafb5258a34 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/TaskPersisterTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.test.AndroidTestCase; +import android.util.SparseBooleanArray; + +/** + * Tests for {@link TaskPersister}. + * + * Build/Install/Run: + * atest WmTests:TaskPersisterTest + */ +public class TaskPersisterTest extends AndroidTestCase { + private static final String TEST_USER_NAME = "AM-Test-User"; + + private TaskPersister mTaskPersister; + private int testUserId; + private UserManager mUserManager; + + @Override + public void setUp() throws Exception { + super.setUp(); + mUserManager = UserManager.get(getContext()); + mTaskPersister = new TaskPersister(getContext().getFilesDir()); + testUserId = createUser(TEST_USER_NAME, 0); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + mTaskPersister.unloadUserDataFromMemory(testUserId); + removeUser(testUserId); + } + + private int getRandomTaskIdForUser(int userId) { + int taskId = (int) (Math.random() * UserHandle.PER_USER_RANGE); + taskId += UserHandle.PER_USER_RANGE * userId; + return taskId; + } + + public void testTaskIdsPersistence() { + SparseBooleanArray taskIdsOnFile = new SparseBooleanArray(); + for (int i = 0; i < 100; i++) { + taskIdsOnFile.put(getRandomTaskIdForUser(testUserId), true); + } + mTaskPersister.writePersistedTaskIdsForUser(taskIdsOnFile, testUserId); + SparseBooleanArray newTaskIdsOnFile = mTaskPersister + .loadPersistedTaskIdsForUser(testUserId); + assertTrue("TaskIds written differ from TaskIds read back from file", + taskIdsOnFile.equals(newTaskIdsOnFile)); + } + + private int createUser(String name, int flags) { + UserInfo user = mUserManager.createUser(name, flags); + if (user == null) { + fail("Error while creating the test user: " + TEST_USER_NAME); + } + return user.id; + } + + private void removeUser(int userId) { + if (!mUserManager.removeUser(userId)) { + fail("Error while removing the test user: " + TEST_USER_NAME); + } + } +} \ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/am/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/am/TaskRecordTests.java new file mode 100644 index 000000000000..dbb3e7ee4183 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/TaskRecordTests.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.server.am; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.service.voice.IVoiceInteractionSession; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Xml; + +import com.android.internal.app.IVoiceInteractor; +import com.android.server.am.TaskRecord.TaskRecordFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.util.ArrayList; + +/** + * Tests for exercising {@link TaskRecord}. + * + * Build/Install/Run: + * atest WmTests:TaskRecordTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskRecordTests extends ActivityTestsBase { + + private static final String TASK_TAG = "task"; + + private ActivityManagerService mService; + + @Before + public void setUp() throws Exception { + super.setUp(); + TaskRecord.setTaskRecordFactory(null); + mService = createActivityManagerService(); + } + + @Test + public void testRestoreWindowedTask() throws Exception { + final TaskRecord expected = createTaskRecord(64); + expected.mLastNonFullscreenBounds = new Rect(50, 50, 100, 100); + + final File serializedFile = serializeToFile(expected); + + try { + final TaskRecord actual = restoreFromFile(serializedFile); + assertEquals(expected.taskId, actual.taskId); + assertEquals(expected.mLastNonFullscreenBounds, actual.mLastNonFullscreenBounds); + } finally { + serializedFile.delete(); + } + } + + @Test + public void testDefaultTaskFactoryNotNull() throws Exception { + assertNotNull(TaskRecord.getTaskRecordFactory()); + } + + @Test + public void testCreateTestRecordUsingCustomizedFactory() throws Exception { + TestTaskRecordFactory factory = new TestTaskRecordFactory(); + TaskRecord.setTaskRecordFactory(factory); + + assertFalse(factory.mCreated); + + TaskRecord.create(null, 0, null, null, null, null); + + assertTrue(factory.mCreated); + } + + @Test + public void testReturnsToHomeStack() throws Exception { + final TaskRecord task = createTaskRecord(1); + assertFalse(task.returnsToHomeStack()); + task.intent = null; + assertFalse(task.returnsToHomeStack()); + task.intent = new Intent(); + assertFalse(task.returnsToHomeStack()); + task.intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME); + assertTrue(task.returnsToHomeStack()); + } + + private File serializeToFile(TaskRecord r) throws IOException, XmlPullParserException { + final File tmpFile = File.createTempFile(r.taskId + "_task_", "xml"); + + try (final OutputStream os = new FileOutputStream(tmpFile)) { + final XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(os, "UTF-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TASK_TAG); + r.saveToXml(serializer); + serializer.endTag(null, TASK_TAG); + serializer.endDocument(); + } + + return tmpFile; + } + + private TaskRecord restoreFromFile(File file) throws IOException, XmlPullParserException { + try (final Reader reader = new BufferedReader(new FileReader(file))) { + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(reader); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(TASK_TAG, parser.getName()); + return TaskRecord.restoreFromXml(parser, mService.mStackSupervisor); + } + } + + private TaskRecord createTaskRecord(int taskId) { + return new TaskRecord(mService, taskId, new Intent(), null, null, null, null, null, false, + false, false, 0, 10050, null, new ArrayList<>(), 0, false, null, 0, 0, 0, 0, 0, + null, 0, false, false, false, 0, 0); + } + + private static class TestTaskRecordFactory extends TaskRecordFactory { + private boolean mCreated = false; + + @Override + TaskRecord create(ActivityManagerService service, int taskId, ActivityInfo info, + Intent intent, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { + mCreated = true; + return null; + } + + @Override + TaskRecord create(ActivityManagerService service, int taskId, ActivityInfo info, + Intent intent, + ActivityManager.TaskDescription taskDescription) { + mCreated = true; + return null; + } + + @Override + TaskRecord create(ActivityManagerService service, int taskId, Intent intent, + Intent affinityIntent, String affinity, String rootAffinity, + ComponentName realActivity, + ComponentName origActivity, boolean rootWasReset, boolean autoRemoveRecents, + boolean askedCompatMode, int userId, int effectiveUid, String lastDescription, + ArrayList activities, long lastTimeMoved, + boolean neverRelinquishIdentity, + ActivityManager.TaskDescription lastTaskDescription, + int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, + int callingUid, String callingPackage, int resizeMode, + boolean supportsPictureInPicture, + boolean realActivitySuspended, boolean userSetupComplete, int minWidth, + int minHeight) { + mCreated = true; + return null; + } + + @Override + TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor) + throws IOException, XmlPullParserException { + mCreated = true; + return null; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/am/TaskStackChangedListenerTest.java new file mode 100644 index 000000000000..28689cb701c9 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/TaskStackChangedListenerTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityManager.TaskDescription; +import android.app.IActivityManager; +import android.app.ITaskStackListener; +import android.app.Instrumentation.ActivityMonitor; +import android.app.TaskStackListener; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.RemoteException; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; +import android.text.TextUtils; + +import com.android.internal.annotations.GuardedBy; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link TaskStackListener}. + * + * Build/Install/Run: + * atest WmTests:TaskStackChangedListenerTest + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class TaskStackChangedListenerTest { + + private IActivityManager mService; + private ITaskStackListener mTaskStackListener; + + private static final Object sLock = new Object(); + @GuardedBy("sLock") + private static boolean sTaskStackChangedCalled; + private static boolean sActivityBResumed; + + @Before + public void setUp() throws Exception { + mService = ActivityManager.getService(); + } + + @After + public void tearDown() throws Exception { + mService.unregisterTaskStackListener(mTaskStackListener); + mTaskStackListener = null; + } + + @Test + public void testTaskStackChanged_afterFinish() throws Exception { + registerTaskStackChangedListener(new TaskStackListener() { + @Override + public void onTaskStackChanged() throws RemoteException { + synchronized (sLock) { + sTaskStackChangedCalled = true; + } + } + }); + + Context ctx = InstrumentationRegistry.getContext(); + ctx.startActivity(new Intent(ctx, ActivityA.class)); + UiDevice.getInstance(getInstrumentation()).waitForIdle(); + synchronized (sLock) { + Assert.assertTrue(sTaskStackChangedCalled); + } + Assert.assertTrue(sActivityBResumed); + } + + @Test + public void testTaskDescriptionChanged() throws Exception { + final Object[] params = new Object[2]; + final CountDownLatch latch = new CountDownLatch(1); + registerTaskStackChangedListener(new TaskStackListener() { + int taskId = -1; + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) + throws RemoteException { + this.taskId = taskId; + } + @Override + public void onTaskDescriptionChanged(int taskId, TaskDescription td) + throws RemoteException { + if (this.taskId == taskId && !TextUtils.isEmpty(td.getLabel())) { + params[0] = taskId; + params[1] = td; + latch.countDown(); + } + } + }); + final Activity activity = startTestActivity(ActivityTaskDescriptionChange.class); + waitForCallback(latch); + assertEquals(activity.getTaskId(), params[0]); + assertEquals("Test Label", ((TaskDescription) params[1]).getLabel()); + } + + @Test + public void testActivityRequestedOrientationChanged() throws Exception { + final int[] params = new int[2]; + final CountDownLatch latch = new CountDownLatch(1); + registerTaskStackChangedListener(new TaskStackListener() { + @Override + public void onActivityRequestedOrientationChanged(int taskId, + int requestedOrientation) { + params[0] = taskId; + params[1] = requestedOrientation; + latch.countDown(); + } + }); + final Activity activity = startTestActivity(ActivityRequestedOrientationChange.class); + waitForCallback(latch); + assertEquals(activity.getTaskId(), params[0]); + assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]); + } + + @Test + /** + * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted. + */ + public void testTaskChangeCallBacks() throws Exception { + final Object[] params = new Object[2]; + final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1); + final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1); + final CountDownLatch taskRemovedLatch = new CountDownLatch(1); + final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1); + final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1); + registerTaskStackChangedListener(new TaskStackListener() { + @Override + public void onTaskCreated(int taskId, ComponentName componentName) + throws RemoteException { + params[0] = taskId; + params[1] = componentName; + taskCreatedLaunchLatch.countDown(); + } + + @Override + public void onTaskMovedToFront(int taskId) throws RemoteException { + params[0] = taskId; + taskMovedToFrontLatch.countDown(); + } + + @Override + public void onTaskRemovalStarted(int taskId) { + params[0] = taskId; + taskRemovalStartedLatch.countDown(); + } + + @Override + public void onTaskRemoved(int taskId) throws RemoteException { + params[0] = taskId; + taskRemovedLatch.countDown(); + } + }); + + final ActivityTaskChangeCallbacks activity = + (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class); + final int id = activity.getTaskId(); + + // Test for onTaskCreated. + waitForCallback(taskCreatedLaunchLatch); + assertEquals(id, params[0]); + ComponentName componentName = (ComponentName) params[1]; + assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName()); + + // Test for onTaskMovedToFront. + assertEquals(1, taskMovedToFrontLatch.getCount()); + mService.moveTaskToFront(id, 0, null); + waitForCallback(taskMovedToFrontLatch); + assertEquals(activity.getTaskId(), params[0]); + + // Test for onTaskRemovalStarted. + assertEquals(1, taskRemovalStartedLatch.getCount()); + activity.finishAndRemoveTask(); + waitForCallback(taskRemovalStartedLatch); + // onTaskRemovalStarted happens before the activity's window is removed. + assertFalse(activity.onDetachedFromWindowCalled); + assertEquals(id, params[0]); + + // Test for onTaskRemoved. + assertEquals(1, taskRemovedLatch.getCount()); + waitForCallback(taskRemovedLatch); + assertEquals(id, params[0]); + assertTrue(activity.onDetachedFromWindowCalled); + } + + /** + * Starts the provided activity and returns the started instance. + */ + private Activity startTestActivity(Class activityClass) { + final Context context = InstrumentationRegistry.getContext(); + final ActivityMonitor monitor = + new ActivityMonitor(activityClass.getName(), null, false); + InstrumentationRegistry.getInstrumentation().addMonitor(monitor); + context.startActivity(new Intent(context, activityClass)); + final Activity activity = monitor.waitForActivityWithTimeout(1000); + if (activity == null) { + throw new RuntimeException("Timed out waiting for Activity"); + } + return activity; + } + + private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception { + mTaskStackListener = listener; + mService.registerTaskStackListener(listener); + } + + private void waitForCallback(CountDownLatch latch) { + try { + final boolean result = latch.await(2, TimeUnit.SECONDS); + if (!result) { + throw new RuntimeException("Timed out waiting for task stack change notification"); + } + }catch (InterruptedException e) {} + } + + public static class ActivityA extends Activity { + + private boolean mActivityBLaunched = false; + + @Override + protected void onPostResume() { + super.onPostResume(); + if (mActivityBLaunched) { + return; + } + mActivityBLaunched = true; + finish(); + startActivity(new Intent(this, ActivityB.class)); + } + } + + public static class ActivityB extends Activity { + + @Override + protected void onPostResume() { + super.onPostResume(); + synchronized (sLock) { + sTaskStackChangedCalled = false; + } + sActivityBResumed = true; + finish(); + } + } + + public static class ActivityRequestedOrientationChange extends Activity { + @Override + protected void onPostResume() { + super.onPostResume(); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + finish(); + } + } + + public static class ActivityTaskDescriptionChange extends Activity { + @Override + protected void onPostResume() { + super.onPostResume(); + setTaskDescription(new TaskDescription("Test Label")); + finish(); + } + } + + public static class ActivityTaskChangeCallbacks extends Activity { + public boolean onDetachedFromWindowCalled = false;; + + @Override + public void onDetachedFromWindow() { + onDetachedFromWindowCalled = true; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/am/UserControllerTest.java b/services/tests/wmtests/src/com/android/server/am/UserControllerTest.java new file mode 100644 index 000000000000..fb5ded51489f --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/am/UserControllerTest.java @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.am; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; + +import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG; +import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG; +import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG; +import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG; +import static com.android.server.am.UserController.SYSTEM_USER_CURRENT_MSG; +import static com.android.server.am.UserController.SYSTEM_USER_START_MSG; +import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import android.app.IUserSwitchObserver; +import android.content.Context; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IRemoteCallback; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserManagerInternal; +import android.platform.test.annotations.Presubmit; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import com.android.server.pm.UserManagerService; +import com.android.server.wm.WindowManagerService; + +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Tests for {@link UserController}. + * + * Build/Install/Run: + * atest WmTests:UserControllerTest + */ +@Presubmit +public class UserControllerTest extends AndroidTestCase { + private static final int TEST_USER_ID = 10; + private static final int NONEXIST_USER_ID = 2; + private static String TAG = UserControllerTest.class.getSimpleName(); + private UserController mUserController; + private TestInjector mInjector; + + private static final List START_FOREGROUND_USER_ACTIONS = + Arrays.asList( + Intent.ACTION_USER_STARTED, + Intent.ACTION_USER_SWITCHED, + Intent.ACTION_USER_STARTING); + + private static final List START_BACKGROUND_USER_ACTIONS = + Arrays.asList( + Intent.ACTION_USER_STARTED, + Intent.ACTION_LOCKED_BOOT_COMPLETED, + Intent.ACTION_USER_STARTING); + + private static final Set START_FOREGROUND_USER_MESSAGE_CODES = + new HashSet<>(Arrays.asList(REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, + SYSTEM_USER_START_MSG, SYSTEM_USER_CURRENT_MSG)); + + private static final Set START_BACKGROUND_USER_MESSAGE_CODES = + new HashSet<>(Arrays.asList(SYSTEM_USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG)); + + @Override + public void setUp() throws Exception { + super.setUp(); + runWithDexmakerShareClassLoader(() -> { + mInjector = Mockito.spy(new TestInjector(getContext())); + doNothing().when(mInjector).clearAllLockedTasks(anyString()); + doNothing().when(mInjector).startHomeActivity(anyInt(), anyString()); + doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any()); + doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity(); + mUserController = new UserController(mInjector); + setUpUser(TEST_USER_ID, 0); + }); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mInjector.handlerThread.quit(); + Mockito.validateMockitoUsage(); + } + + @SmallTest + public void testStartUser_foreground() throws RemoteException { + mUserController.startUser(TEST_USER_ID, true /* foreground */); + Mockito.verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt()); + Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); + Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); + Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true); + Mockito.verify(mInjector).clearAllLockedTasks(anyString()); + startForegroundUserAssertions(); + } + + @SmallTest + public void testStartUser_background() throws RemoteException { + mUserController.startUser(TEST_USER_ID, false /* foreground */); + Mockito.verify( + mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); + Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); + Mockito.verify(mInjector, never()).clearAllLockedTasks(anyString()); + startBackgroundUserAssertions(); + } + + @SmallTest + public void testStartUserUIDisabled() throws RemoteException { + mUserController.mUserSwitchUiEnabled = false; + mUserController.startUser(TEST_USER_ID, true /* foreground */); + Mockito.verify(mInjector.getWindowManager(), never()) + .startFreezingScreen(anyInt(), anyInt()); + Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); + Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); + startForegroundUserAssertions(); + } + + private void startUserAssertions( + List expectedActions, Set expectedMessageCodes) + throws RemoteException { + assertEquals(expectedActions, getActions(mInjector.sentIntents)); + Set actualCodes = mInjector.handler.getMessageCodes(); + assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes); + } + + private void startBackgroundUserAssertions() throws RemoteException { + startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES); + } + + private void startForegroundUserAssertions() throws RemoteException { + startUserAssertions(START_FOREGROUND_USER_ACTIONS, START_FOREGROUND_USER_MESSAGE_CODES); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + assertNotNull(userState); + assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier()); + assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); + assertEquals("Unexpected old user id", 0, reportMsg.arg1); + assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2); + } + + @SmallTest + public void testFailedStartUserInForeground() throws RemoteException { + mUserController.mUserSwitchUiEnabled = false; + mUserController.startUserInForeground(NONEXIST_USER_ID); + Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); + Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(false); + } + + @SmallTest + public void testDispatchUserSwitch() throws RemoteException { + // Prepare mock observer and register it + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + doAnswer(invocation -> { + IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1]; + callback.sendResult(null); + return null; + }).when(observer).onUserSwitching(anyInt(), any()); + mUserController.registerUserSwitchObserver(observer, "mock"); + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + // Call dispatchUserSwitch and verify that observer was called only once + mInjector.handler.clearAllRecordedMessages(); + mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); + Set expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG); + Set actualCodes = mInjector.handler.getMessageCodes(); + assertEquals("Unexpected message sent", expectedCodes, actualCodes); + Message conMsg = mInjector.handler.getMessageForCode(CONTINUE_USER_SWITCH_MSG); + assertNotNull(conMsg); + userState = (UserState) conMsg.obj; + assertNotNull(userState); + assertEquals(TEST_USER_ID, userState.mHandle.getIdentifier()); + assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); + assertEquals("Unexpected old user id", 0, conMsg.arg1); + assertEquals("Unexpected new user id", TEST_USER_ID, conMsg.arg2); + } + + @SmallTest + public void testDispatchUserSwitchBadReceiver() throws RemoteException { + // Prepare mock observer which doesn't notify the callback and register it + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + mUserController.registerUserSwitchObserver(observer, "mock"); + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + // Call dispatchUserSwitch and verify that observer was called only once + mInjector.handler.clearAllRecordedMessages(); + mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); + // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout) + Set actualCodes = mInjector.handler.getMessageCodes(); + assertTrue("No messages should be sent", actualCodes.isEmpty()); + } + + @SmallTest + public void testContinueUserSwitch() throws RemoteException { + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + mInjector.handler.clearAllRecordedMessages(); + // Verify that continueUserSwitch worked as expected + mUserController.continueUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); + continueUserSwitchAssertions(); + } + + @SmallTest + public void testContinueUserSwitchUIDisabled() throws RemoteException { + mUserController.mUserSwitchUiEnabled = false; + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + UserState userState = (UserState) reportMsg.obj; + int oldUserId = reportMsg.arg1; + int newUserId = reportMsg.arg2; + mInjector.handler.clearAllRecordedMessages(); + // Verify that continueUserSwitch worked as expected + mUserController.continueUserSwitch(userState, oldUserId, newUserId); + Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); + continueUserSwitchAssertions(); + } + + private void continueUserSwitchAssertions() throws RemoteException { + Set expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG); + Set actualCodes = mInjector.handler.getMessageCodes(); + assertEquals("Unexpected message sent", expectedCodes, actualCodes); + Message msg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG); + assertNotNull(msg); + assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1); + } + + @SmallTest + public void testDispatchUserSwitchComplete() throws RemoteException { + // Prepare mock observer and register it + IUserSwitchObserver observer = mock(IUserSwitchObserver.class); + when(observer.asBinder()).thenReturn(new Binder()); + mUserController.registerUserSwitchObserver(observer, "mock"); + // Start user -- this will update state of mUserController + mUserController.startUser(TEST_USER_ID, true); + Message reportMsg = mInjector.handler.getMessageForCode(REPORT_USER_SWITCH_MSG); + assertNotNull(reportMsg); + int newUserId = reportMsg.arg2; + mInjector.handler.clearAllRecordedMessages(); + // Mockito can't reset only interactions, so just verify that this hasn't been + // called with 'false' until after dispatchUserSwitchComplete. + Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(false); + // Call dispatchUserSwitchComplete + mUserController.dispatchUserSwitchComplete(newUserId); + Mockito.verify(observer, times(1)).onUserSwitchComplete(anyInt()); + Mockito.verify(observer).onUserSwitchComplete(TEST_USER_ID); + Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(false); + } + + private void setUpUser(int userId, int flags) { + UserInfo userInfo = new UserInfo(userId, "User" + userId, flags); + when(mInjector.userManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); + } + + private static List getActions(List intents) { + List result = new ArrayList<>(); + for (Intent intent : intents) { + result.add(intent.getAction()); + } + return result; + } + + // Should be public to allow mocking + public static class TestInjector extends UserController.Injector { + TestHandler handler; + TestHandler uiHandler; + HandlerThread handlerThread; + UserManagerService userManagerMock; + UserManagerInternal userManagerInternalMock; + WindowManagerService windowManagerMock; + private Context mCtx; + List sentIntents = new ArrayList<>(); + + TestInjector(Context ctx) { + super(null); + mCtx = ctx; + handlerThread = new HandlerThread(TAG); + handlerThread.start(); + handler = new TestHandler(handlerThread.getLooper()); + uiHandler = new TestHandler(handlerThread.getLooper()); + userManagerMock = mock(UserManagerService.class); + userManagerInternalMock = mock(UserManagerInternal.class); + windowManagerMock = mock(WindowManagerService.class); + } + + @Override + protected Handler getHandler(Handler.Callback callback) { + return handler; + } + + @Override + protected Handler getUiHandler(Handler.Callback callback) { + return uiHandler; + } + + @Override + protected UserManagerService getUserManager() { + return userManagerMock; + } + + @Override + UserManagerInternal getUserManagerInternal() { + return userManagerInternalMock; + } + + @Override + protected Context getContext() { + return mCtx; + } + + @Override + int checkCallingPermission(String permission) { + Log.i(TAG, "checkCallingPermission " + permission); + return PERMISSION_GRANTED; + } + + @Override + WindowManagerService getWindowManager() { + return windowManagerMock; + } + + @Override + void updateUserConfiguration() { + Log.i(TAG, "updateUserConfiguration"); + } + + @Override + protected int broadcastIntent(Intent intent, String resolvedType, + IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras, + String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered, + boolean sticky, int callingPid, int callingUid, int userId) { + Log.i(TAG, "broadcastIntentLocked " + intent); + sentIntents.add(intent); + return 0; + } + + @Override + void reportGlobalUsageEventLocked(int event) { + } + + @Override + void reportCurWakefulnessUsageEvent() { + } + } + + private static class TestHandler extends Handler { + private final List mMessages = new ArrayList<>(); + + TestHandler(Looper looper) { + super(looper); + } + + Set getMessageCodes() { + Set result = new LinkedHashSet<>(); + for (Message msg : mMessages) { + result.add(msg.what); + } + return result; + } + + Message getMessageForCode(int what) { + for (Message msg : mMessages) { + if (msg.what == what) { + return msg; + } + } + return null; + } + + void clearAllRecordedMessages() { + mMessages.clear(); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + Message copy = new Message(); + copy.copyFrom(msg); + mMessages.add(copy); + return super.sendMessageAtTime(msg, uptimeMillis); + } + } +} \ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/policy/FakeWindowState.java b/services/tests/wmtests/src/com/android/server/policy/FakeWindowState.java new file mode 100644 index 000000000000..7487d4490d9a --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/FakeWindowState.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import android.annotation.Nullable; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.proto.ProtoOutputStream; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.IApplicationToken; +import android.view.WindowManager; + +import com.android.server.wm.utils.WmDisplayCutout; + +public class FakeWindowState implements WindowManagerPolicy.WindowState { + + public final Rect parentFrame = new Rect(); + public final Rect displayFrame = new Rect(); + public final Rect overscanFrame = new Rect(); + public final Rect contentFrame = new Rect(); + public final Rect visibleFrame = new Rect(); + public final Rect decorFrame = new Rect(); + public final Rect stableFrame = new Rect(); + public Rect outsetFrame = new Rect(); + + public WmDisplayCutout displayCutout; + + public WindowManager.LayoutParams attrs; + public int displayId; + public boolean isVoiceInteraction; + public boolean inMultiWindowMode; + public boolean visible = true; + public int surfaceLayer = 1; + public boolean isDimming = false; + + public boolean policyVisible = true; + + @Override + public int getOwningUid() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public String getOwningPackage() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, + Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame, + @Nullable Rect outsetFrame, WmDisplayCutout displayCutout, + boolean parentFrameWasClippedByDisplayCutout) { + this.parentFrame.set(parentFrame); + this.displayFrame.set(displayFrame); + this.overscanFrame.set(overlayFrame); + this.contentFrame.set(contentFrame); + this.visibleFrame.set(visibleFrame); + this.decorFrame.set(decorFrame); + this.stableFrame.set(stableFrame); + this.outsetFrame = outsetFrame == null ? null : new Rect(outsetFrame); + this.displayCutout = displayCutout; + } + + @Override + public Rect getFrameLw() { + return parentFrame; + } + + @Override + public Rect getDisplayFrameLw() { + return displayFrame; + } + + @Override + public Rect getOverscanFrameLw() { + return overscanFrame; + } + + @Override + public Rect getContentFrameLw() { + return contentFrame; + } + + @Override + public Rect getVisibleFrameLw() { + return visibleFrame; + } + + @Override + public boolean getGivenInsetsPendingLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Rect getGivenContentInsetsLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Rect getGivenVisibleInsetsLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public WindowManager.LayoutParams getAttrs() { + return attrs; + } + + @Override + public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getSystemUiVisibility() { + return attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility; + } + + @Override + public int getSurfaceLayer() { + return surfaceLayer; + } + + @Override + public int getBaseType() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public IApplicationToken getAppToken() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isVoiceInteraction() { + return isVoiceInteraction; + } + + @Override + public boolean hasAppShownWindows() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isVisibleLw() { + return visible && policyVisible; + } + + @Override + public boolean isDisplayedLw() { + return isVisibleLw(); + } + + @Override + public boolean isAnimatingLw() { + return false; + } + + @Override + public boolean canAffectSystemUiFlags() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isGoneForLayoutLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isDrawnLw() { + return true; + } + + @Override + public boolean hasDrawnLw() { + return true; + } + + @Override + public boolean hideLw(boolean doAnimation) { + if (!policyVisible) { + return false; + } + policyVisible = false; + return true; + } + + @Override + public boolean showLw(boolean doAnimation) { + if (policyVisible) { + return false; + } + policyVisible = true; + return true; + } + + @Override + public boolean isAlive() { + return true; + } + + @Override + public boolean isDefaultDisplay() { + return displayId == Display.DEFAULT_DISPLAY; + } + + @Override + public boolean isDimming() { + return isDimming; + } + + @Override + public int getWindowingMode() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isInMultiWindowMode() { + return inMultiWindowMode; + } + + @Override + public int getRotationAnimationHint() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isInputMethodWindow() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getDisplayId() { + return displayId; + } + + @Override + public boolean canAcquireSleepToken() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId){ + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isInputMethodTarget() { + return false; + } +} diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java new file mode 100644 index 000000000000..7e18ce78c9d1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerInsetsTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Display; +import android.view.DisplayInfo; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class PhoneWindowManagerInsetsTest extends PhoneWindowManagerTestBase { + + @Rule + public final ErrorCollector mErrorCollector = new ErrorCollector(); + + @Before + public void setUp() throws Exception { + addStatusBar(); + addNavigationBar(); + } + + @Test + public void portrait() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */); + + verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); + verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT); + verifyConsistency(di); + } + + @Test + public void portrait_withCutout() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */); + + verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); + verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT); + verifyConsistency(di); + } + + @Test + public void landscape() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */); + + verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + verifyNonDecorInsets(di, 0, 0, NAV_BAR_HEIGHT, 0); + verifyConsistency(di); + } + + @Test + public void landscape_withCutout() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */); + + verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + verifyNonDecorInsets(di, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT, 0); + verifyConsistency(di); + } + + @Test + public void seascape() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */); + + verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); + verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, 0, 0); + verifyConsistency(di); + } + + @Test + public void seascape_withCutout() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */); + + verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); + verifyNonDecorInsets(di, NAV_BAR_HEIGHT, 0, DISPLAY_CUTOUT_HEIGHT, 0); + verifyConsistency(di); + } + + @Test + public void upsideDown() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */); + + verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT); + verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT); + verifyConsistency(di); + } + + @Test + public void upsideDown_withCutout() throws Exception { + DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */); + + verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT); + verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT); + verifyConsistency(di); + } + + private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) { + mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect( + left, top, right, bottom))); + } + + private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) { + mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(di), equalTo(new Rect( + left, top, right, bottom))); + } + + private void verifyConsistency(DisplayInfo di) { + verifyConsistency("configDisplay", di, getStableInsetsLw(di), + getConfigDisplayWidth(di), getConfigDisplayHeight(di)); + verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di), + getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di)); + } + + private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width, + int height) { + mErrorCollector.checkThat(what + ":width", width, + equalTo(di.logicalWidth - insets.left - insets.right)); + mErrorCollector.checkThat(what + ":height", height, + equalTo(di.logicalHeight - insets.top - insets.bottom)); + } + + private Rect getStableInsetsLw(DisplayInfo di) { + Rect result = new Rect(); + mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + di.displayCutout, result); + return result; + } + + private Rect getNonDecorInsetsLw(DisplayInfo di) { + Rect result = new Rect(); + mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, + di.displayCutout, result); + return result; + } + + private int getNonDecorDisplayWidth(DisplayInfo di) { + return mPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight, di.rotation, + 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); + } + + private int getNonDecorDisplayHeight(DisplayInfo di) { + return mPolicy.getNonDecorDisplayHeight(di.logicalWidth, di.logicalHeight, di.rotation, + 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); + } + + private int getConfigDisplayWidth(DisplayInfo di) { + return mPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight, di.rotation, + 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); + } + + private int getConfigDisplayHeight(DisplayInfo di) { + return mPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight, di.rotation, + 0 /* ui */, Display.DEFAULT_DISPLAY, di.displayCutout); + } +} \ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java new file mode 100644 index 000000000000..97a716f6bd99 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; +import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.DisplayCutout; +import android.view.WindowManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { + + private FakeWindowState mAppWindow; + + @Before + public void setUp() throws Exception { + mAppWindow = new FakeWindowState(); + mAppWindow.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, + TYPE_APPLICATION, + FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + PixelFormat.TRANSLUCENT); + + addStatusBar(); + addNavigationBar(); + } + + @Test + public void layoutWindowLw_appDrawsBars() throws Exception { + mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + assertInsetBy(mAppWindow.displayFrame, 0, 0, 0, 0); + } + + @Test + public void layoutWindowLw_appWontDrawBars() throws Exception { + mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.displayFrame, 0, NAV_BAR_HEIGHT); + } + + @Test + public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception { + mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.displayFrame, 0, NAV_BAR_HEIGHT); + } + + @Test + public void addingWindow_doesNotTamperWithSysuiFlags() { + mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mPolicy.addWindow(mAppWindow); + + assertEquals(0, mAppWindow.attrs.systemUiVisibility); + assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility); + } + + @Test + public void layoutWindowLw_withDisplayCutout() { + addDisplayCutout(); + + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.displayFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withhDisplayCutout_never() { + addDisplayCutout(); + + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_layoutFullscreen() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + assertInsetBy(mAppWindow.displayFrame, 0, 0, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.displayFrame, 0, 0); + } + + + @Test + public void layoutWindowLw_withDisplayCutout_landscape() { + addDisplayCutout(); + setRotation(ROTATION_90); + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.contentFrame, + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + assertInsetBy(mAppWindow.displayFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_seascape() { + addDisplayCutout(); + setRotation(ROTATION_270); + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetBy(mAppWindow.stableFrame, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); + assertInsetBy(mAppWindow.contentFrame, + NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + assertInsetBy(mAppWindow.displayFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() { + addDisplayCutout(); + setRotation(ROTATION_90); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.contentFrame, + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_floatingInScreen() { + addDisplayCutout(); + + mAppWindow.attrs.flags = FLAG_LAYOUT_IN_SCREEN; + mAppWindow.attrs.type = TYPE_APPLICATION_OVERLAY; + mAppWindow.attrs.width = DISPLAY_WIDTH; + mAppWindow.attrs.height = DISPLAY_HEIGHT; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.displayFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() { + addDisplayCutout(); + setRotation(ROTATION_90); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, 0, 0, 0, 0); + assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.contentFrame, + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + } + + @Test + public void layoutHint_screenDecorWindow() { + addDisplayCutout(); + mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_IS_SCREEN_DECOR; + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + final Rect frame = new Rect(); + final Rect content = new Rect(); + final Rect stable = new Rect(); + final Rect outsets = new Rect(); + final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper(); + mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content, + stable, outsets, cutout); + + assertThat(frame, equalTo(mFrames.mUnrestricted)); + assertThat(content, equalTo(new Rect())); + assertThat(stable, equalTo(new Rect())); + assertThat(outsets, equalTo(new Rect())); + assertThat(cutout.get(), equalTo(DisplayCutout.NO_CUTOUT)); + } + + @Test + public void layoutHint_appWindow() { + // Initialize DisplayFrames + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + final Rect outFrame = new Rect(); + final Rect outContentInsets = new Rect(); + final Rect outStableInsets = new Rect(); + final Rect outOutsets = new Rect(); + final DisplayCutout.ParcelableWrapper outDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + + mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets, + outStableInsets, outOutsets, outDisplayCutout); + + assertThat(outFrame, is(mFrames.mUnrestricted)); + assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); + assertThat(outStableInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); + assertThat(outOutsets, is(new Rect())); + assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + } + + @Test + public void layoutHint_appWindowInTask() { + // Initialize DisplayFrames + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + final Rect taskBounds = new Rect(100, 100, 200, 200); + + final Rect outFrame = new Rect(); + final Rect outContentInsets = new Rect(); + final Rect outStableInsets = new Rect(); + final Rect outOutsets = new Rect(); + final DisplayCutout.ParcelableWrapper outDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + + mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets, + outStableInsets, outOutsets, outDisplayCutout); + + assertThat(outFrame, is(taskBounds)); + assertThat(outContentInsets, is(new Rect())); + assertThat(outStableInsets, is(new Rect())); + assertThat(outOutsets, is(new Rect())); + assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + } +} \ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTest.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTest.java new file mode 100644 index 000000000000..30665b56c8dc --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; + +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM; +import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.graphics.PixelFormat; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.WindowManager; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class PhoneWindowManagerTest { + + private static FakeWindowState createOpaqueFullscreen(boolean hasLightNavBar) { + final FakeWindowState state = new FakeWindowState(); + state.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, + TYPE_BASE_APPLICATION, + FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + PixelFormat.OPAQUE); + state.attrs.subtreeSystemUiVisibility = + hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0; + return state; + } + + private static FakeWindowState createDimmingDialogWindow(boolean canBeImTarget) { + final FakeWindowState state = new FakeWindowState(); + state.attrs = new WindowManager.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, + TYPE_APPLICATION, + FLAG_DIM_BEHIND | (canBeImTarget ? 0 : FLAG_ALT_FOCUSABLE_IM), + PixelFormat.TRANSLUCENT); + state.isDimming = true; + return state; + } + + private static FakeWindowState createInputMethodWindow(boolean visible, boolean drawNavBar, + boolean hasLightNavBar) { + final FakeWindowState state = new FakeWindowState(); + state.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, + TYPE_INPUT_METHOD, + FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN + | (drawNavBar ? FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS : 0), + PixelFormat.TRANSPARENT); + state.attrs.subtreeSystemUiVisibility = + hasLightNavBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0; + state.visible = visible; + state.policyVisible = visible; + return state; + } + + + @Test + public void testChooseNavigationColorWindowLw() throws Exception { + final FakeWindowState opaque = createOpaqueFullscreen(false); + + final FakeWindowState dimmingImTarget = createDimmingDialogWindow(true); + final FakeWindowState dimmingNonImTarget = createDimmingDialogWindow(false); + + final FakeWindowState visibleIme = createInputMethodWindow(true, true, false); + final FakeWindowState invisibleIme = createInputMethodWindow(false, true, false); + final FakeWindowState imeNonDrawNavBar = createInputMethodWindow(true, false, false); + + // If everything is null, return null + assertNull(null, PhoneWindowManager.chooseNavigationColorWindowLw( + null, null, null, NAV_BAR_BOTTOM)); + + assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, opaque, null, NAV_BAR_BOTTOM)); + assertEquals(dimmingImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, dimmingImTarget, null, NAV_BAR_BOTTOM)); + assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, dimmingNonImTarget, null, NAV_BAR_BOTTOM)); + + assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( + null, null, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( + null, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( + null, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, opaque, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(visibleIme, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM)); + assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM)); + + assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, opaque, invisibleIme, NAV_BAR_BOTTOM)); + assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, opaque, invisibleIme, NAV_BAR_BOTTOM)); + assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, opaque, visibleIme, NAV_BAR_RIGHT)); + + // Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color + // window. + assertEquals(opaque, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, opaque, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + assertEquals(dimmingImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + assertEquals(dimmingNonImTarget, PhoneWindowManager.chooseNavigationColorWindowLw( + opaque, dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM)); + } + + @Test + public void testUpdateLightNavigationBarLw() throws Exception { + final FakeWindowState opaqueDarkNavBar = createOpaqueFullscreen(false); + final FakeWindowState opaqueLightNavBar = createOpaqueFullscreen(true); + + final FakeWindowState dimming = createDimmingDialogWindow(false); + + final FakeWindowState imeDrawDarkNavBar = createInputMethodWindow(true,true, false); + final FakeWindowState imeDrawLightNavBar = createInputMethodWindow(true,true, true); + + assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, + PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, + null, null)); + + // Opaque top fullscreen window overrides SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR flag. + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + 0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar)); + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, opaqueDarkNavBar, null, + opaqueDarkNavBar)); + assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, + PhoneWindowManager.updateLightNavigationBarLw(0, opaqueLightNavBar, + opaqueLightNavBar, null, opaqueLightNavBar)); + assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, + PhoneWindowManager.updateLightNavigationBarLw(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, + opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar)); + + // Dimming window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR. + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + 0, opaqueDarkNavBar, dimming, null, dimming)); + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + 0, opaqueLightNavBar, dimming, null, dimming)); + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueDarkNavBar, dimming, null, dimming)); + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, null, dimming)); + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, dimming, imeDrawLightNavBar, + dimming)); + + // IME window clears SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, null, null, imeDrawDarkNavBar, + imeDrawDarkNavBar)); + + // Even if the top fullscreen has SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, IME window wins. + assertEquals(0, PhoneWindowManager.updateLightNavigationBarLw( + SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, opaqueLightNavBar, opaqueLightNavBar, + imeDrawDarkNavBar, imeDrawDarkNavBar)); + + // IME window should be able to use SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR. + assertEquals(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, + PhoneWindowManager.updateLightNavigationBarLw(0, opaqueDarkNavBar, + opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar)); + } + + @Test + public void testIsDockSideAllowedDockTop() throws Exception { + // Docked top is always allowed + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_TOP, DOCKED_LEFT, NAV_BAR_BOTTOM, + true /* navigationBarCanMove */)); + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_TOP, DOCKED_LEFT, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + } + + @Test + public void testIsDockSideAllowedDockBottom() throws Exception { + // Cannot dock bottom + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_BOTTOM, DOCKED_LEFT, NAV_BAR_BOTTOM, + true /* navigationBarCanMove */)); + } + + @Test + public void testIsDockSideAllowedNavigationBarMovable() throws Exception { + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_BOTTOM, + true /* navigationBarCanMove */)); + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_LEFT, + true /* navigationBarCanMove */)); + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_RIGHT, + true /* navigationBarCanMove */)); + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_BOTTOM, + true /* navigationBarCanMove */)); + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_RIGHT, + true /* navigationBarCanMove */)); + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_LEFT, + true /* navigationBarCanMove */)); + } + + @Test + public void testIsDockSideAllowedNavigationBarNotMovable() throws Exception { + // Navigation bar is not movable such as tablets + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_LEFT, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_TOP, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_LEFT, DOCKED_RIGHT, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_LEFT, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + assertFalse(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_TOP, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + assertTrue(PhoneWindowManager.isDockSideAllowed(DOCKED_RIGHT, DOCKED_RIGHT, NAV_BAR_BOTTOM, + false /* navigationBarCanMove */)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTestBase.java new file mode 100644 index 000000000000..2c47a9432eff --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; + +import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.UserHandle; +import android.support.test.InstrumentationRegistry; +import android.testing.TestableResources; +import android.util.Pair; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; + +import com.android.server.policy.keyguard.KeyguardServiceDelegate; +import com.android.server.wm.DisplayFrames; +import com.android.server.wm.utils.WmDisplayCutout; + +import org.junit.Before; + +public class PhoneWindowManagerTestBase { + static final int DISPLAY_WIDTH = 500; + static final int DISPLAY_HEIGHT = 1000; + + static final int STATUS_BAR_HEIGHT = 10; + static final int NAV_BAR_HEIGHT = 15; + static final int DISPLAY_CUTOUT_HEIGHT = 8; + + TestablePhoneWindowManager mPolicy; + TestContextWrapper mContext; + DisplayFrames mFrames; + + FakeWindowState mStatusBar; + FakeWindowState mNavigationBar; + private boolean mHasDisplayCutout; + private int mRotation = ROTATION_0; + + @Before + public void setUpBase() throws Exception { + mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext()); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.navigation_bar_width, NAV_BAR_HEIGHT); + + mPolicy = TestablePhoneWindowManager.create(mContext); + + updateDisplayFrames(); + } + + public void setRotation(int rotation) { + mRotation = rotation; + updateDisplayFrames(); + } + + private void updateDisplayFrames() { + Pair info = displayInfoAndCutoutForRotation(mRotation, + mHasDisplayCutout); + mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info.first, info.second); + } + + public void addStatusBar() { + mStatusBar = new FakeWindowState(); + mStatusBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT, + TYPE_STATUS_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); + mStatusBar.attrs.gravity = Gravity.TOP; + + mPolicy.addWindow(mStatusBar); + mPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT; + } + + public void addNavigationBar() { + mNavigationBar = new FakeWindowState(); + mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT, + TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); + mNavigationBar.attrs.gravity = Gravity.BOTTOM; + + mPolicy.addWindow(mNavigationBar); + mPolicy.mHasNavigationBar = true; + mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT; + } + + public void addDisplayCutout() { + mHasDisplayCutout = true; + updateDisplayFrames(); + } + + /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */ + public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop, + int expectedInsetRight, int expectedInsetBottom) { + assertEquals(new Rect(expectedInsetLeft, expectedInsetTop, + mFrames.mDisplayWidth - expectedInsetRight, + mFrames.mDisplayHeight - expectedInsetBottom), actual); + } + + /** + * Asserts that {@code actual} is inset by the given amounts from the full display rect. + * + * Convenience wrapper for when only the top and bottom inset are non-zero. + */ + public void assertInsetByTopBottom(Rect actual, int expectedInsetTop, int expectedInsetBottom) { + assertInsetBy(actual, 0, expectedInsetTop, 0, expectedInsetBottom); + } + + public static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) { + return displayInfoAndCutoutForRotation(rotation, withDisplayCutout).first; + } + public static Pair displayInfoAndCutoutForRotation(int rotation, + boolean withDisplayCutout) { + DisplayInfo info = new DisplayInfo(); + WmDisplayCutout cutout = null; + + final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270; + info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + info.rotation = rotation; + if (withDisplayCutout) { + cutout = WmDisplayCutout.computeSafeInsets( + displayCutoutForRotation(rotation), info.logicalWidth, + info.logicalHeight); + info.displayCutout = cutout.getDisplayCutout(); + } else { + info.displayCutout = null; + } + return Pair.create(info, cutout); + } + + private static DisplayCutout displayCutoutForRotation(int rotation) { + Path p = new Path(); + p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT, + Path.Direction.CCW); + + Matrix m = new Matrix(); + transformPhysicalToLogicalCoordinates(rotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, m); + p.transform(m); + + return DisplayCutout.fromBounds(p); + } + + static class TestContextWrapper extends ContextWrapper { + private final TestableResources mResourceMocker; + + public TestContextWrapper(Context targetContext) { + super(targetContext); + mResourceMocker = new TestableResources(targetContext.getResources()); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public Resources getResources() { + return mResourceMocker.getResources(); + } + + public TestableResources getResourceMocker() { + return mResourceMocker; + } + } + + static class TestablePhoneWindowManager extends PhoneWindowManager { + + public TestablePhoneWindowManager() { + } + + @Override + void initializeHdmiState() { + // Do nothing. + } + + @Override + Context getSystemUiContext() { + return mContext; + } + + void addWindow(WindowState state) { + if (state instanceof FakeWindowState) { + ((FakeWindowState) state).surfaceLayer = + getWindowLayerFromTypeLw(state.getAttrs().type, + true /* canAddInternalSystemWindow */); + } + adjustWindowParamsLw(state, state.getAttrs(), true /* hasStatusBarPermission */); + assertEquals(WindowManagerGlobal.ADD_OKAY, prepareAddWindowLw(state, state.getAttrs())); + } + + public static TestablePhoneWindowManager create(Context context) { + TestablePhoneWindowManager[] policy = new TestablePhoneWindowManager[1]; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + policy[0] = new TestablePhoneWindowManager(); + policy[0].mContext = context; + policy[0].mKeyguardDelegate = mock(KeyguardServiceDelegate.class); + policy[0].mAccessibilityManager = new AccessibilityManager(context, + mock(IAccessibilityManager.class), UserHandle.USER_CURRENT); + policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class); + policy[0].mNavigationBarCanMove = true; + policy[0].mPortraitRotation = ROTATION_0; + policy[0].mLandscapeRotation = ROTATION_90; + policy[0].mUpsideDownRotation = ROTATION_180; + policy[0].mSeascapeRotation = ROTATION_270; + policy[0].onConfigurationChanged(); + }); + return policy[0]; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java new file mode 100644 index 000000000000..a396946db247 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for the {@link TaskStack} class. + * + * Build/Install/Run: + * atest WmTests:AnimatingAppWindowTokenRegistryTest + */ +@SmallTest +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class AnimatingAppWindowTokenRegistryTest extends WindowTestsBase { + + @Mock + AnimationAdapter mAdapter; + + @Mock + Runnable mMockEndDeferFinishCallback1; + @Mock + Runnable mMockEndDeferFinishCallback2; + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + } + + @Test + public void testDeferring() throws Exception { + final AppWindowToken window1 = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD, + "window2").mAppToken; + final AnimatingAppWindowTokenRegistry registry = + window1.getStack().getAnimatingAppWindowTokenRegistry(); + + window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + assertTrue(window1.isSelfAnimating()); + assertTrue(window2.isSelfAnimating()); + + // Make sure that first animation finish is deferred, second one is not deferred, and first + // one gets cancelled. + assertTrue(registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1)); + assertFalse(registry.notifyAboutToFinish(window2, mMockEndDeferFinishCallback2)); + verify(mMockEndDeferFinishCallback1).run(); + verifyZeroInteractions(mMockEndDeferFinishCallback2); + } + + @Test + public void testContainerRemoved() throws Exception { + final AppWindowToken window1 = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD, + "window2").mAppToken; + final AnimatingAppWindowTokenRegistry registry = + window1.getStack().getAnimatingAppWindowTokenRegistry(); + + window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + assertTrue(window1.isSelfAnimating()); + assertTrue(window2.isSelfAnimating()); + + // Make sure that first animation finish is deferred, and removing the second window stops + // finishes all pending deferred finishings. + registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1); + window2.setParent(null); + verify(mMockEndDeferFinishCallback1).run(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java new file mode 100644 index 000000000000..6b3266d2f0a0 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link AppTransition}. + * + * Build/Install/Run: + * atest WmTests:AppTransitionTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class AppTransitionTests { + + @Rule + public final WindowManagerServiceRule mRule = new WindowManagerServiceRule(); + private WindowManagerService mWm; + + @Before + public void setUp() throws Exception { + final Context context = InstrumentationRegistry.getTargetContext(); + mWm = mRule.getWindowManagerService(); + } + + @Test + public void testKeyguardOverride() throws Exception { + mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); + mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); + assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition()); + } + + @Test + public void testKeyguardKeep() throws Exception { + mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); + mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); + assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition()); + } + + @Test + public void testForceOverride() throws Exception { + mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */); + mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */, + 0 /* flags */, true /* forceOverride */); + assertEquals(TRANSIT_ACTIVITY_OPEN, mWm.mAppTransition.getAppTransition()); + } + + @Test + public void testCrashing() throws Exception { + mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); + mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); + assertEquals(TRANSIT_CRASHING_ACTIVITY_CLOSE, mWm.mAppTransition.getAppTransition()); + } + + @Test + public void testKeepKeyguard_withCrashing() throws Exception { + mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */); + mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); + assertEquals(TRANSIT_KEYGUARD_GOING_AWAY, mWm.mAppTransition.getAppTransition()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowContainerControllerTests.java new file mode 100644 index 000000000000..09a084737def --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowContainerControllerTests.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.res.Configuration.EMPTY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.wm.WindowTestUtils.TestTaskWindowContainerController; + +import org.junit.Test; + +/** + * Test class for {@link AppWindowContainerController}. + * + * Build/Install/Run: + * atest WmTests:AppWindowContainerControllerTests + */ +@SmallTest +@Presubmit +@FlakyTest(bugId = 74078662) +@org.junit.runner.RunWith(AndroidJUnit4.class) +public class AppWindowContainerControllerTests extends WindowTestsBase { + + @Test + public void testRemoveContainer() throws Exception { + final WindowTestUtils.TestAppWindowContainerController controller = + createAppWindowController(); + + // Assert token was added to display. + assertNotNull(mDisplayContent.getWindowToken(controller.mToken.asBinder())); + // Assert that the container was created and linked. + assertNotNull(controller.mContainer); + + controller.removeContainer(mDisplayContent.getDisplayId()); + + // Assert token was remove from display. + assertNull(mDisplayContent.getWindowToken(controller.mToken.asBinder())); + // Assert that the container was removed. + assertNull(controller.mContainer); + } + + @Test + public void testSetOrientation() throws Exception { + final WindowTestUtils.TestAppWindowContainerController controller = + createAppWindowController(); + + // Assert orientation is unspecified to start. + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation()); + + controller.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getDisplayId(), + EMPTY /* displayConfig */, false /* freezeScreenIfNeeded */); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, controller.getOrientation()); + + controller.removeContainer(mDisplayContent.getDisplayId()); + // Assert orientation is unspecified to after container is removed. + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, controller.getOrientation()); + + // Reset display frozen state + sWm.mDisplayFrozen = false; + } + + private void assertHasStartingWindow(AppWindowToken atoken) { + assertNotNull(atoken.startingSurface); + assertNotNull(atoken.startingData); + assertNotNull(atoken.startingWindow); + } + + private void assertNoStartingWindow(AppWindowToken atoken) { + assertNull(atoken.startingSurface); + assertNull(atoken.startingWindow); + assertNull(atoken.startingData); + atoken.forAllWindows(windowState -> { + assertFalse(windowState.getBaseType() == TYPE_APPLICATION_STARTING); + }, true); + } + + @Test + public void testCreateRemoveStartingWindow() throws Exception { + final WindowTestUtils.TestAppWindowContainerController controller = + createAppWindowController(); + controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, + false, false); + waitUntilHandlersIdle(); + final AppWindowToken atoken = controller.getAppWindowToken(mDisplayContent); + assertHasStartingWindow(atoken); + controller.removeStartingWindow(); + waitUntilHandlersIdle(); + assertNoStartingWindow(atoken); + } + + @Test + public void testAddRemoveRace() throws Exception { + + // There was once a race condition between adding and removing starting windows + for (int i = 0; i < 1000; i++) { + final WindowTestUtils.TestAppWindowContainerController controller = + createAppWindowController(); + controller.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, + false, false); + controller.removeStartingWindow(); + waitUntilHandlersIdle(); + assertNoStartingWindow(controller.getAppWindowToken(mDisplayContent)); + + controller.getAppWindowToken(mDisplayContent).getParent().getParent().removeImmediately(); + } + } + + @Test + public void testTransferStartingWindow() throws Exception { + final WindowTestUtils.TestAppWindowContainerController controller1 = + createAppWindowController(); + final WindowTestUtils.TestAppWindowContainerController controller2 = + createAppWindowController(); + controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, + false, false); + waitUntilHandlersIdle(); + controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), + true, true, false, true, false, false); + waitUntilHandlersIdle(); + assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent)); + assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent)); + } + + @Test + public void testTransferStartingWindowWhileCreating() throws Exception { + final WindowTestUtils.TestAppWindowContainerController controller1 = + createAppWindowController(); + final WindowTestUtils.TestAppWindowContainerController controller2 = + createAppWindowController(); + ((TestWindowManagerPolicy) sWm.mPolicy).setRunnableWhenAddingSplashScreen(() -> { + + // Surprise, ...! Transfer window in the middle of the creation flow. + controller2.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, controller1.mToken.asBinder(), + true, true, false, true, false, false); + }); + controller1.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, + false, false); + waitUntilHandlersIdle(); + assertNoStartingWindow(controller1.getAppWindowToken(mDisplayContent)); + assertHasStartingWindow(controller2.getAppWindowToken(mDisplayContent)); + } + + @Test + public void testTryTransferStartingWindowFromHiddenAboveToken() throws Exception { + + // Add two tasks on top of each other. + TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(this); + final WindowTestUtils.TestAppWindowContainerController controllerTop = + createAppWindowController(taskController); + final WindowTestUtils.TestAppWindowContainerController controllerBottom = + createAppWindowController(taskController); + + // Add a starting window. + controllerTop.addStartingWindow(InstrumentationRegistry.getContext().getPackageName(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, + false, false); + waitUntilHandlersIdle(); + + // Make the top one invisible, and try transfering the starting window from the top to the + // bottom one. + controllerTop.setVisibility(false, false); + controllerBottom.mContainer.transferStartingWindowFromHiddenAboveTokenIfNeeded(); + + // Assert that the bottom window now has the starting window. + assertNoStartingWindow(controllerTop.getAppWindowToken(mDisplayContent)); + assertHasStartingWindow(controllerBottom.getAppWindowToken(mDisplayContent)); + } + + @Test + public void testReparent() throws Exception { + final StackWindowController stackController = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTaskWindowContainerController taskController1 = + new WindowTestUtils.TestTaskWindowContainerController(stackController); + final WindowTestUtils.TestAppWindowContainerController appWindowController1 = + createAppWindowController(taskController1); + final WindowTestUtils.TestTaskWindowContainerController taskController2 = + new WindowTestUtils.TestTaskWindowContainerController(stackController); + final WindowTestUtils.TestAppWindowContainerController appWindowController2 = + createAppWindowController(taskController2); + final WindowTestUtils.TestTaskWindowContainerController taskController3 = + new WindowTestUtils.TestTaskWindowContainerController(stackController); + + try { + appWindowController1.reparent(taskController1, 0); + fail("Should not be able to reparent to the same parent"); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + taskController3.setContainer(null); + appWindowController1.reparent(taskController3, 0); + fail("Should not be able to reparent to a task that doesn't have a container"); + } catch (IllegalArgumentException e) { + // Expected + } + + // Reparent the app window and ensure that it is moved + appWindowController1.reparent(taskController2, 0); + assertEquals(taskController2.mContainer, appWindowController1.mContainer.getParent()); + assertEquals(0, ((WindowTestUtils.TestAppWindowToken) appWindowController1.mContainer) + .positionInParent()); + assertEquals(1, ((WindowTestUtils.TestAppWindowToken) appWindowController2.mContainer) + .positionInParent()); + } + + private WindowTestUtils.TestAppWindowContainerController createAppWindowController() { + return createAppWindowController( + new WindowTestUtils.TestTaskWindowContainerController(this)); + } + + private WindowTestUtils.TestAppWindowContainerController createAppWindowController( + WindowTestUtils.TestTaskWindowContainerController taskController) { + return new WindowTestUtils.TestAppWindowContainerController(taskController); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java new file mode 100644 index 000000000000..8dedb584ae59 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.TRANSIT_UNSET; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Surface; +import android.view.WindowManager; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link AppWindowToken} class. + * + * Build/Install/Run: + * atest WmTests:AppWindowTokenTests + */ +@SmallTest +// TODO: b/68267650 +// @Presubmit +@RunWith(AndroidJUnit4.class) +public class AppWindowTokenTests extends WindowTestsBase { + + TaskStack mStack; + Task mTask; + WindowTestUtils.TestAppWindowToken mToken; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mStack = createTaskStackOnDisplay(mDisplayContent); + mTask = createTaskInStack(mStack, 0 /* userId */); + mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + + mTask.addChild(mToken, 0); + } + + @Test + @Presubmit + public void testAddWindow_Order() throws Exception { + assertEquals(0, mToken.getWindowsCount()); + + final WindowState win1 = createWindow(null, TYPE_APPLICATION, mToken, "win1"); + final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, mToken, + "startingWin"); + final WindowState baseWin = createWindow(null, TYPE_BASE_APPLICATION, mToken, "baseWin"); + final WindowState win4 = createWindow(null, TYPE_APPLICATION, mToken, "win4"); + + // Should not contain the windows that were added above. + assertEquals(4, mToken.getWindowsCount()); + assertTrue(mToken.hasWindow(win1)); + assertTrue(mToken.hasWindow(startingWin)); + assertTrue(mToken.hasWindow(baseWin)); + assertTrue(mToken.hasWindow(win4)); + + // The starting window should be on-top of all other windows. + assertEquals(startingWin, mToken.getLastChild()); + + // The base application window should be below all other windows. + assertEquals(baseWin, mToken.getFirstChild()); + mToken.removeImmediately(); + } + + @Test + @Presubmit + public void testFindMainWindow() throws Exception { + assertNull(mToken.findMainWindow()); + + final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1"); + final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window11"); + final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, mToken, "window12"); + assertEquals(window1, mToken.findMainWindow()); + window1.mAnimatingExit = true; + assertEquals(window1, mToken.findMainWindow()); + final WindowState window2 = createWindow(null, TYPE_APPLICATION_STARTING, mToken, "window2"); + assertEquals(window2, mToken.findMainWindow()); + mToken.removeImmediately(); + } + + @Test + @Presubmit + public void testGetTopFullscreenWindow() throws Exception { + assertNull(mToken.getTopFullscreenWindow()); + + final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mToken, "window1"); + final WindowState window11 = createWindow(null, TYPE_APPLICATION, mToken, "window11"); + final WindowState window12 = createWindow(null, TYPE_APPLICATION, mToken, "window12"); + assertEquals(window12, mToken.getTopFullscreenWindow()); + window12.mAttrs.width = 500; + assertEquals(window11, mToken.getTopFullscreenWindow()); + window11.mAttrs.width = 500; + assertEquals(window1, mToken.getTopFullscreenWindow()); + mToken.removeImmediately(); + } + + @Test + public void testLandscapeSeascapeRotationByApp() throws Exception { + // Some plumbing to get the service ready for rotation updates. + sWm.mDisplayReady = true; + sWm.mDisplayEnabled = true; + + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( + TYPE_BASE_APPLICATION); + attrs.setTitle("AppWindow"); + final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); + mToken.addWindow(appWindow); + + // Set initial orientation and update. + mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null, + mDisplayContent.getDisplayId()); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation()); + appWindow.resizeReported = false; + + // Update the orientation to perform 180 degree rotation and check that resize was reported. + mToken.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE); + sWm.updateOrientationFromAppTokens(mDisplayContent.getOverrideConfiguration(), null, + mDisplayContent.getDisplayId()); + sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); + assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mDisplayContent.getLastOrientation()); + assertTrue(appWindow.resizeReported); + appWindow.removeImmediately(); + } + + @Test + public void testLandscapeSeascapeRotationByPolicy() throws Exception { + // Some plumbing to get the service ready for rotation updates. + sWm.mDisplayReady = true; + sWm.mDisplayEnabled = true; + + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( + TYPE_BASE_APPLICATION); + attrs.setTitle("AppWindow"); + final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); + mToken.addWindow(appWindow); + + // Set initial orientation and update. + performRotation(Surface.ROTATION_90); + appWindow.resizeReported = false; + + // Update the rotation to perform 180 degree rotation and check that resize was reported. + performRotation(Surface.ROTATION_270); + assertTrue(appWindow.resizeReported); + appWindow.removeImmediately(); + } + + private void performRotation(int rotationToReport) { + ((TestWindowManagerPolicy) sWm.mPolicy).rotationToReport = rotationToReport; + sWm.updateRotation(false, false); + // Simulate animator finishing orientation change + sWm.mRoot.mOrientationChangeComplete = true; + sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); + } + + @Test + @Presubmit + public void testGetOrientation() throws Exception { + mToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + mToken.setFillsParent(false); + // Can specify orientation if app doesn't fill parent. + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation()); + + mToken.setFillsParent(true); + mToken.setHidden(true); + mToken.sendingToBottom = true; + // Can not specify orientation if app isn't visible even though it fills parent. + assertEquals(SCREEN_ORIENTATION_UNSET, mToken.getOrientation()); + // Can specify orientation if the current orientation candidate is orientation behind. + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mToken.getOrientation(SCREEN_ORIENTATION_BEHIND)); + } + + @Test + @Presubmit + public void testKeyguardFlagsDuringRelaunch() throws Exception { + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams( + TYPE_BASE_APPLICATION); + attrs.flags |= FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD; + attrs.setTitle("AppWindow"); + final WindowTestUtils.TestWindowState appWindow = createWindowState(attrs, mToken); + + // Add window with show when locked flag + mToken.addWindow(appWindow); + assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow()); + + // Start relaunching + mToken.startRelaunching(); + assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow()); + + // Remove window and make sure that we still report back flag + mToken.removeChild(appWindow); + assertTrue(mToken.containsShowWhenLockedWindow() && mToken.containsDismissKeyguardWindow()); + + // Finish relaunching and ensure flag is now not reported + mToken.finishRelaunching(); + assertFalse(mToken.containsShowWhenLockedWindow() || mToken.containsDismissKeyguardWindow()); + } + + @Test + @FlakyTest(detail = "Promote once confirmed non-flaky") + public void testStuckExitingWindow() throws Exception { + final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, + "closingWindow"); + closingWindow.mAnimatingExit = true; + closingWindow.mRemoveOnExit = true; + closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, + true /* performLayout */, false /* isVoiceInteraction */); + + // We pretended that we were running an exit animation, but that should have been cleared up + // by changing visibility of AppWindowToken + closingWindow.removeIfPossible(); + assertTrue(closingWindow.mRemoved); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java new file mode 100644 index 000000000000..7fbb4ee3b0b4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; +import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; +import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; +import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.wm.BoundsAnimationController.BoundsAnimator; +import com.android.server.wm.WindowManagerInternal.AppTransitionListener; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks + * depending on the various interactions. + * + * We are really concerned about only three of the transition states [F = fullscreen, !F = floating] + * F->!F, !F->!F, and !F->F. Each animation can only be cancelled from the target mid-transition, + * or if a new animation starts on the same target. The tests below verifies that the target is + * notified of all the cases where it is animating and cancelled so that it can respond + * appropriately. + * + * Build/Install/Run: + * bit WmTests:BoundsAnimationControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class BoundsAnimationControllerTests extends WindowTestsBase { + + /** + * Mock value animator to simulate updates with. + */ + private class MockValueAnimator extends ValueAnimator { + + private float mFraction; + + public MockValueAnimator getWithValue(float fraction) { + mFraction = fraction; + return this; + } + + @Override + public Object getAnimatedValue() { + return mFraction; + } + } + + /** + * Mock app transition to fire notifications to the bounds animator. + */ + private class MockAppTransition extends AppTransition { + + private AppTransitionListener mListener; + + MockAppTransition(Context context) { + super(context, null); + } + + @Override + void registerListenerLocked(AppTransitionListener listener) { + mListener = listener; + } + + public void notifyTransitionPending() { + mListener.onAppTransitionPendingLocked(); + } + + public void notifyTransitionCancelled(int transit) { + mListener.onAppTransitionCancelledLocked(transit); + } + + public void notifyTransitionStarting(int transit) { + mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0); + } + + public void notifyTransitionFinished() { + mListener.onAppTransitionFinishedLocked(null); + } + } + + /** + * A test animate bounds user to track callbacks from the bounds animation. + */ + private class TestBoundsAnimationTarget implements BoundsAnimationTarget { + + boolean mAwaitingAnimationStart; + boolean mMovedToFullscreen; + boolean mAnimationStarted; + boolean mSchedulePipModeChangedOnStart; + boolean mForcePipModeChangedCallback; + boolean mAnimationEnded; + Rect mAnimationEndFinalStackBounds; + boolean mSchedulePipModeChangedOnEnd; + boolean mBoundsUpdated; + boolean mCancelRequested; + Rect mStackBounds; + Rect mTaskBounds; + + void initialize(Rect from) { + mAwaitingAnimationStart = true; + mMovedToFullscreen = false; + mAnimationStarted = false; + mAnimationEnded = false; + mAnimationEndFinalStackBounds = null; + mForcePipModeChangedCallback = false; + mSchedulePipModeChangedOnStart = false; + mSchedulePipModeChangedOnEnd = false; + mStackBounds = from; + mTaskBounds = null; + mBoundsUpdated = false; + } + + @Override + public void onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) { + mAwaitingAnimationStart = false; + mAnimationStarted = true; + mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback; + mForcePipModeChangedCallback = forceUpdate; + } + + @Override + public boolean shouldDeferStartOnMoveToFullscreen() { + return true; + } + + @Override + public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) { + // TODO: Once we break the runs apart, we should fail() here if this is called outside + // of onAnimationStart() and onAnimationEnd() + if (mCancelRequested) { + mCancelRequested = false; + return false; + } else { + mBoundsUpdated = true; + mStackBounds = stackBounds; + mTaskBounds = taskBounds; + return true; + } + } + + @Override + public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds, + boolean moveToFullscreen) { + mAnimationEnded = true; + mAnimationEndFinalStackBounds = finalStackBounds; + mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback; + mMovedToFullscreen = moveToFullscreen; + mTaskBounds = null; + } + } + + /** + * Drives the animations, makes common assertions along the way. + */ + private class BoundsAnimationDriver { + + private BoundsAnimationController mController; + private TestBoundsAnimationTarget mTarget; + private BoundsAnimator mAnimator; + + private Rect mFrom; + private Rect mTo; + private Rect mLargerBounds; + private Rect mExpectedFinalBounds; + + BoundsAnimationDriver(BoundsAnimationController controller, + TestBoundsAnimationTarget target) { + mController = controller; + mTarget = target; + } + + BoundsAnimationDriver start(Rect from, Rect to) { + if (mAnimator != null) { + throw new IllegalArgumentException("Call restart() to restart an animation"); + } + + boolean fromFullscreen = from.equals(BOUNDS_FULL); + boolean toFullscreen = to.equals(BOUNDS_FULL); + + mTarget.initialize(from); + + // Started, not running + assertTrue(mTarget.mAwaitingAnimationStart); + assertTrue(!mTarget.mAnimationStarted); + + startImpl(from, to); + + // Ensure that the animator is paused for the all windows drawn signal when animating + // to/from fullscreen + if (fromFullscreen || toFullscreen) { + assertTrue(mAnimator.isPaused()); + mController.onAllWindowsDrawn(); + } else { + assertTrue(!mAnimator.isPaused()); + } + + // Started and running + assertTrue(!mTarget.mAwaitingAnimationStart); + assertTrue(mTarget.mAnimationStarted); + + return this; + } + + BoundsAnimationDriver restart(Rect to, boolean expectStartedAndPipModeChangedCallback) { + if (mAnimator == null) { + throw new IllegalArgumentException("Call start() to start a new animation"); + } + + BoundsAnimator oldAnimator = mAnimator; + boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo); + + // Reset the animation start state + mTarget.mAnimationStarted = false; + + // Start animation + startImpl(mTarget.mStackBounds, to); + + if (toSameBounds) { + // Same animator if same final bounds + assertSame(oldAnimator, mAnimator); + } + + if (expectStartedAndPipModeChangedCallback) { + // Replacing animation with pending pip mode changed callback, ensure we update + assertTrue(mTarget.mAnimationStarted); + assertTrue(mTarget.mSchedulePipModeChangedOnStart); + assertTrue(mTarget.mForcePipModeChangedCallback); + } else { + // No animation start for replacing animation + assertTrue(!mTarget.mAnimationStarted); + } + mTarget.mAnimationStarted = true; + return this; + } + + private BoundsAnimationDriver startImpl(Rect from, Rect to) { + boolean fromFullscreen = from.equals(BOUNDS_FULL); + boolean toFullscreen = to.equals(BOUNDS_FULL); + mFrom = new Rect(from); + mTo = new Rect(to); + mExpectedFinalBounds = new Rect(to); + mLargerBounds = getLargerBounds(mFrom, mTo); + + // Start animation + final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen + ? SCHEDULE_PIP_MODE_CHANGED_ON_START + : fromFullscreen + ? SCHEDULE_PIP_MODE_CHANGED_ON_END + : NO_PIP_MODE_CHANGED_CALLBACKS; + mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION, + schedulePipModeChangedState, fromFullscreen, toFullscreen); + + // Original stack bounds, frozen task bounds + assertEquals(mFrom, mTarget.mStackBounds); + assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); + + // Animating to larger size + if (mFrom.equals(mLargerBounds)) { + assertTrue(!mAnimator.animatingToLargerSize()); + } else if (mTo.equals(mLargerBounds)) { + assertTrue(mAnimator.animatingToLargerSize()); + } + + return this; + } + + BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) { + // Callback made + assertTrue(mTarget.mAnimationStarted); + + assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart); + return this; + } + + BoundsAnimationDriver update(float t) { + mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t)); + + // Temporary stack bounds, frozen task bounds + if (t == 0f) { + assertEquals(mFrom, mTarget.mStackBounds); + } else if (t == 1f) { + assertEquals(mTo, mTarget.mStackBounds); + } else { + assertNotEquals(mFrom, mTarget.mStackBounds); + assertNotEquals(mTo, mTarget.mStackBounds); + } + assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); + return this; + } + + BoundsAnimationDriver cancel() { + // Cancel + mTarget.mCancelRequested = true; + mTarget.mBoundsUpdated = false; + mExpectedFinalBounds = null; + + // Update + mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f)); + + // Not started, not running, cancel reset + assertTrue(!mTarget.mCancelRequested); + + // Stack/task bounds not updated + assertTrue(!mTarget.mBoundsUpdated); + + // Callback made + assertTrue(mTarget.mAnimationEnded); + assertNull(mTarget.mAnimationEndFinalStackBounds); + + return this; + } + + BoundsAnimationDriver end() { + mAnimator.end(); + + // Final stack bounds + assertEquals(mTo, mTarget.mStackBounds); + assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds); + assertNull(mTarget.mTaskBounds); + + return this; + } + + BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged, + boolean moveToFullscreen) { + // Callback made + assertTrue(mTarget.mAnimationEnded); + + assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd); + assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen); + return this; + } + + private Rect getLargerBounds(Rect r1, Rect r2) { + int r1Area = r1.width() * r1.height(); + int r2Area = r2.width() * r2.height(); + if (r1Area <= r2Area) { + return r2; + } else { + return r1; + } + } + } + + // Constants + private static final boolean SCHEDULE_PIP_MODE_CHANGED = true; + private static final boolean MOVE_TO_FULLSCREEN = true; + private static final int DURATION = 100; + + // Some dummy bounds to represent fullscreen and floating bounds + private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100); + private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95); + private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95); + + // Common + private MockAppTransition mMockAppTransition; + private MockValueAnimator mMockAnimator; + private TestBoundsAnimationTarget mTarget; + private BoundsAnimationController mController; + private BoundsAnimationDriver mDriver; + + // Temp + private Rect mTmpRect = new Rect(); + + @Override + public void setUp() throws Exception { + super.setUp(); + + final Context context = InstrumentationRegistry.getTargetContext(); + final Handler handler = new Handler(Looper.getMainLooper()); + mMockAppTransition = new MockAppTransition(context); + mMockAnimator = new MockValueAnimator(); + mTarget = new TestBoundsAnimationTarget(); + mController = new BoundsAnimationController(context, mMockAppTransition, handler, null); + mDriver = new BoundsAnimationDriver(mController, mTarget); + } + + /** BASE TRANSITIONS **/ + + @UiThreadTest + @Test + public void testFullscreenToFloatingTransition() throws Exception { + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0f) + .update(0.5f) + .update(1f) + .end() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFloatingToFullscreenTransition() throws Exception { + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + .expectStarted(SCHEDULE_PIP_MODE_CHANGED) + .update(0f) + .update(0.5f) + .update(1f) + .end() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFloatingToSmallerFloatingTransition() throws Exception { + mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0f) + .update(0.5f) + .update(1f) + .end() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFloatingToLargerFloatingTransition() throws Exception { + mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0f) + .update(0.5f) + .update(1f) + .end() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + /** F->!F w/ CANCEL **/ + + @UiThreadTest + @Test + public void testFullscreenToFloatingCancelFromTarget() throws Exception { + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .cancel() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception { + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */) + .end() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception { + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .restart(BOUNDS_SMALLER_FLOATING, + false /* expectStartedAndPipModeChangedCallback */) + .end() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception { + // When animating from fullscreen and the animation is interruped, we expect the animation + // start callback to be made, with a forced pip mode change callback + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */) + .end() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); + } + + /** !F->F w/ CANCEL **/ + + @UiThreadTest + @Test + public void testFloatingToFullscreenCancelFromTarget() throws Exception { + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + .expectStarted(SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .cancel() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception { + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + .expectStarted(SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */) + .end() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception { + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + .expectStarted(SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .restart(BOUNDS_SMALLER_FLOATING, + false /* expectStartedAndPipModeChangedCallback */) + .end() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN); + } + + /** !F->!F w/ CANCEL **/ + + @UiThreadTest + @Test + public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception { + mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .cancel() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + @UiThreadTest + @Test + public void testFloatingToLargerFloatingCancelFromTarget() throws Exception { + mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0.25f) + .cancel() + .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + + /** MISC **/ + + @UiThreadTest + @Test + public void testBoundsAreCopied() throws Exception { + Rect from = new Rect(0, 0, 100, 100); + Rect to = new Rect(25, 25, 75, 75); + mDriver.start(from, to) + .update(0.25f) + .end(); + assertEquals(new Rect(0, 0, 100, 100), from); + assertEquals(new Rect(25, 25, 75, 75), to); + } + + /** + * @return whether the task and stack bounds would be the same if they were at the same offset. + */ + private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) { + mTmpRect.set(taskBounds); + mTmpRect.offsetTo(stackBounds.left, stackBounds.top); + return stackBounds.equals(mTmpRect); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java new file mode 100644 index 000000000000..6a6f9543842f --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ConfigurationContainerTests.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; +import static android.content.res.Configuration.EMPTY; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.content.res.Configuration; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test class for {@link ConfigurationContainer}. + * + * Build/Install/Run: + * atest WmTests:ConfigurationContainerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ConfigurationContainerTests { + + @Test + public void testConfigurationInit() throws Exception { + // Check root container initial config. + final TestConfigurationContainer root = new TestConfigurationContainer(); + assertEquals(EMPTY, root.getOverrideConfiguration()); + assertEquals(EMPTY, root.getMergedOverrideConfiguration()); + assertEquals(EMPTY, root.getConfiguration()); + + // Check child initial config. + final TestConfigurationContainer child1 = root.addChild(); + assertEquals(EMPTY, child1.getOverrideConfiguration()); + assertEquals(EMPTY, child1.getMergedOverrideConfiguration()); + assertEquals(EMPTY, child1.getConfiguration()); + + // Check child initial config if root has overrides. + final Configuration rootOverrideConfig = new Configuration(); + rootOverrideConfig.fontScale = 1.3f; + root.onOverrideConfigurationChanged(rootOverrideConfig); + final TestConfigurationContainer child2 = root.addChild(); + assertEquals(EMPTY, child2.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration()); + assertEquals(rootOverrideConfig, child2.getConfiguration()); + + // Check child initial config if root has parent config set. + final Configuration rootParentConfig = new Configuration(); + rootParentConfig.fontScale = 0.8f; + rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE; + root.onConfigurationChanged(rootParentConfig); + final Configuration rootFullConfig = new Configuration(rootParentConfig); + rootFullConfig.updateFrom(rootOverrideConfig); + + final TestConfigurationContainer child3 = root.addChild(); + assertEquals(EMPTY, child3.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration()); + assertEquals(rootFullConfig, child3.getConfiguration()); + } + + @Test + public void testConfigurationChangeOnAddRemove() throws Exception { + // Init root's config. + final TestConfigurationContainer root = new TestConfigurationContainer(); + final Configuration rootOverrideConfig = new Configuration(); + rootOverrideConfig.fontScale = 1.3f; + root.onOverrideConfigurationChanged(rootOverrideConfig); + + // Init child's config. + final TestConfigurationContainer child = root.addChild(); + final Configuration childOverrideConfig = new Configuration(); + childOverrideConfig.densityDpi = 320; + child.onOverrideConfigurationChanged(childOverrideConfig); + final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration()); + mergedOverrideConfig.updateFrom(childOverrideConfig); + + // Check configuration update when child is removed from parent. + root.removeChild(child); + assertEquals(childOverrideConfig, child.getOverrideConfiguration()); + assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration()); + assertEquals(mergedOverrideConfig, child.getConfiguration()); + + // It may be paranoia... but let's check if parent's config didn't change after removal. + assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getConfiguration()); + + // Init different root + final TestConfigurationContainer root2 = new TestConfigurationContainer(); + final Configuration rootOverrideConfig2 = new Configuration(); + rootOverrideConfig2.fontScale = 1.1f; + root2.onOverrideConfigurationChanged(rootOverrideConfig2); + + // Check configuration update when child is added to different parent. + mergedOverrideConfig.setTo(rootOverrideConfig2); + mergedOverrideConfig.updateFrom(childOverrideConfig); + root2.addChild(child); + assertEquals(childOverrideConfig, child.getOverrideConfiguration()); + assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration()); + assertEquals(mergedOverrideConfig, child.getConfiguration()); + } + + @Test + public void testConfigurationChangePropagation() throws Exception { + // Builds 3-level vertical hierarchy with one configuration container on each level. + // In addition to different overrides on each level, everyone in hierarchy will have one + // common overridden value - orientation; + + // Init root's config. + final TestConfigurationContainer root = new TestConfigurationContainer(); + final Configuration rootOverrideConfig = new Configuration(); + rootOverrideConfig.fontScale = 1.3f; + rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + root.onOverrideConfigurationChanged(rootOverrideConfig); + + // Init children. + final TestConfigurationContainer child1 = root.addChild(); + final Configuration childOverrideConfig1 = new Configuration(); + childOverrideConfig1.densityDpi = 320; + childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE; + child1.onOverrideConfigurationChanged(childOverrideConfig1); + + final TestConfigurationContainer child2 = child1.addChild(); + final Configuration childOverrideConfig2 = new Configuration(); + childOverrideConfig2.screenWidthDp = 150; + childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT; + child2.onOverrideConfigurationChanged(childOverrideConfig2); + + // Check configuration on all levels when root override is updated. + rootOverrideConfig.smallestScreenWidthDp = 200; + root.onOverrideConfigurationChanged(rootOverrideConfig); + + final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig); + mergedOverrideConfig1.updateFrom(childOverrideConfig1); + final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1); + + final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1); + mergedOverrideConfig2.updateFrom(childOverrideConfig2); + final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2); + + assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getConfiguration()); + + assertEquals(childOverrideConfig1, child1.getOverrideConfiguration()); + assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration()); + assertEquals(mergedConfig1, child1.getConfiguration()); + + assertEquals(childOverrideConfig2, child2.getOverrideConfiguration()); + assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration()); + assertEquals(mergedConfig2, child2.getConfiguration()); + + // Check configuration on all levels when root parent config is updated. + final Configuration rootParentConfig = new Configuration(); + rootParentConfig.screenHeightDp = 100; + rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT; + root.onConfigurationChanged(rootParentConfig); + final Configuration mergedRootConfig = new Configuration(rootParentConfig); + mergedRootConfig.updateFrom(rootOverrideConfig); + + mergedConfig1.setTo(mergedRootConfig); + mergedConfig1.updateFrom(mergedOverrideConfig1); + + mergedConfig2.setTo(mergedConfig1); + mergedConfig2.updateFrom(mergedOverrideConfig2); + + assertEquals(rootOverrideConfig, root.getOverrideConfiguration()); + assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration()); + assertEquals(mergedRootConfig, root.getConfiguration()); + + assertEquals(childOverrideConfig1, child1.getOverrideConfiguration()); + assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration()); + assertEquals(mergedConfig1, child1.getConfiguration()); + + assertEquals(childOverrideConfig2, child2.getOverrideConfiguration()); + assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration()); + assertEquals(mergedConfig2, child2.getConfiguration()); + } + + @Test + public void testSetWindowingMode() throws Exception { + final TestConfigurationContainer root = new TestConfigurationContainer(); + root.setWindowingMode(WINDOWING_MODE_UNDEFINED); + final TestConfigurationContainer child = root.addChild(); + child.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(WINDOWING_MODE_UNDEFINED, root.getWindowingMode()); + assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode()); + + root.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + assertEquals(WINDOWING_MODE_FULLSCREEN, root.getWindowingMode()); + assertEquals(WINDOWING_MODE_FREEFORM, child.getWindowingMode()); + } + + @Test + public void testSetActivityType() throws Exception { + final TestConfigurationContainer root = new TestConfigurationContainer(); + root.setActivityType(ACTIVITY_TYPE_UNDEFINED); + final TestConfigurationContainer child = root.addChild(); + child.setActivityType(ACTIVITY_TYPE_STANDARD); + assertEquals(ACTIVITY_TYPE_UNDEFINED, root.getActivityType()); + assertEquals(ACTIVITY_TYPE_STANDARD, child.getActivityType()); + + boolean gotException = false; + try { + // Can't change activity type once set. + child.setActivityType(ACTIVITY_TYPE_HOME); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Can't change activity type once set.", gotException); + + // TODO: Commenting out for now until we figure-out a good way to test these rules that + // should only apply to system process. + /* + gotException = false; + try { + // Parent can't change child's activity type once set. + root.setActivityType(ACTIVITY_TYPE_HOME); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Parent can't change activity type once set.", gotException); + assertEquals(ACTIVITY_TYPE_HOME, root.getActivityType()); + + final TestConfigurationContainer child2 = new TestConfigurationContainer(); + child2.setActivityType(ACTIVITY_TYPE_RECENTS); + + gotException = false; + try { + // Can't re-parent to a different activity type. + root.addChild(child2); + } catch (IllegalStateException e) { + gotException = true; + } + assertTrue("Can't re-parent to a different activity type.", gotException); + */ + + } + + @Test + public void testRegisterConfigurationChangeListener() throws Exception { + final TestConfigurationContainer container = new TestConfigurationContainer(); + final TestConfigurationChangeListener listener = new TestConfigurationChangeListener(); + final Configuration config = new Configuration(); + config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + config.windowConfiguration.setAppBounds(10, 10, 10, 10); + container.onOverrideConfigurationChanged(config); + container.registerConfigurationChangeListener(listener); + // Assert listener got the current config. of the container after it was registered. + assertEquals(config, listener.mOverrideConfiguration); + // Assert listener gets changes to override configuration. + container.onOverrideConfigurationChanged(EMPTY); + assertEquals(EMPTY, listener.mOverrideConfiguration); + } + + /** + * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed + * for testing. + */ + private class TestConfigurationContainer + extends ConfigurationContainer { + private List mChildren = new ArrayList<>(); + private TestConfigurationContainer mParent; + + TestConfigurationContainer addChild(TestConfigurationContainer childContainer) { + childContainer.mParent = this; + childContainer.onParentChanged(); + mChildren.add(childContainer); + return childContainer; + } + + TestConfigurationContainer addChild() { + return addChild(new TestConfigurationContainer()); + } + + void removeChild(TestConfigurationContainer child) { + child.mParent = null; + child.onParentChanged(); + } + + @Override + protected int getChildCount() { + return mChildren.size(); + } + + @Override + protected TestConfigurationContainer getChildAt(int index) { + return mChildren.get(index); + } + + @Override + protected ConfigurationContainer getParent() { + return mParent; + } + } + + private class TestConfigurationChangeListener implements ConfigurationContainerListener { + + final Configuration mOverrideConfiguration = new Configuration(); + + public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + mOverrideConfiguration.setTo(overrideConfiguration); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java new file mode 100644 index 000000000000..a12a802f0920 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link Dimmer}. + * + * Build/Install/Run: + * atest WmTests:DimmerTests; + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class DimmerTests extends WindowTestsBase { + + private class TestWindowContainer extends WindowContainer { + final SurfaceControl mControl = mock(SurfaceControl.class); + final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class); + + TestWindowContainer() { + super(sWm); + } + + @Override + public SurfaceControl getSurfaceControl() { + return mControl; + } + + @Override + public SurfaceControl.Transaction getPendingTransaction() { + return mTransaction; + } + } + + private class MockSurfaceBuildingContainer extends WindowContainer { + final SurfaceSession mSession = new SurfaceSession(); + final SurfaceControl mHostControl = mock(SurfaceControl.class); + final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class); + + MockSurfaceBuildingContainer() { + super(sWm); + } + + class MockSurfaceBuilder extends SurfaceControl.Builder { + MockSurfaceBuilder(SurfaceSession ss) { + super(ss); + } + + @Override + public SurfaceControl build() { + return mock(SurfaceControl.class); + } + } + + @Override + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + return new MockSurfaceBuilder(mSession); + } + + @Override + public SurfaceControl getSurfaceControl() { + return mHostControl; + } + + @Override + public SurfaceControl.Transaction getPendingTransaction() { + return mHostTransaction; + } + } + + private MockSurfaceBuildingContainer mHost; + private Dimmer mDimmer; + private SurfaceControl.Transaction mTransaction; + private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter; + + private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { + @Override + public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, + AnimationAdapter anim, boolean hidden) { + surfaceAnimator.mAnimationFinishedCallback.run(); + } + } + + @Before + public void setUp() throws Exception { + super.setUp(); + mHost = new MockSurfaceBuildingContainer(); + mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); + mTransaction = mock(SurfaceControl.Transaction.class); + mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter); + } + + @Test + public void testDimAboveNoChildCreatesSurface() throws Exception { + final float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, alpha); + + SurfaceControl dimLayer = getDimLayer(); + + assertNotNull("Dimmer should have created a surface", dimLayer); + + verify(mTransaction).setAlpha(dimLayer, alpha); + verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE); + } + + @Test + public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception { + float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, alpha); + final SurfaceControl firstSurface = getDimLayer(); + + alpha = 0.9f; + mDimmer.dimAbove(mTransaction, alpha); + + assertEquals(firstSurface, getDimLayer()); + verify(mTransaction).setAlpha(firstSurface, 0.9f); + } + + @Test + public void testUpdateDimsAppliesSize() throws Exception { + mDimmer.dimAbove(mTransaction, 0.8f); + + int width = 100; + int height = 300; + Rect bounds = new Rect(0, 0, width, height); + mDimmer.updateDims(mTransaction, bounds); + + verify(mTransaction).setSize(getDimLayer(), width, height); + verify(mTransaction).show(getDimLayer()); + } + + @Test + public void testDimAboveNoChildNotReset() throws Exception { + mDimmer.dimAbove(mTransaction, 0.8f); + SurfaceControl dimLayer = getDimLayer(); + mDimmer.resetDimStates(); + + mDimmer.updateDims(mTransaction, new Rect()); + verify(mTransaction).show(getDimLayer()); + verify(dimLayer, never()).destroy(); + } + + @Test + public void testDimAboveWithChildCreatesSurfaceAboveChild() throws Exception { + TestWindowContainer child = new TestWindowContainer(); + mHost.addChild(child, 0); + + final float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, child, alpha); + SurfaceControl dimLayer = getDimLayer(); + + assertNotNull("Dimmer should have created a surface", dimLayer); + + verify(mTransaction).setAlpha(dimLayer, alpha); + verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, 1); + } + + @Test + public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() throws Exception { + TestWindowContainer child = new TestWindowContainer(); + mHost.addChild(child, 0); + + final float alpha = 0.8f; + mDimmer.dimBelow(mTransaction, child, alpha); + SurfaceControl dimLayer = getDimLayer(); + + assertNotNull("Dimmer should have created a surface", dimLayer); + + verify(mTransaction).setAlpha(dimLayer, alpha); + verify(mTransaction).setRelativeLayer(dimLayer, child.mControl, -1); + } + + @Test + public void testDimBelowWithChildSurfaceDestroyedWhenReset() throws Exception { + TestWindowContainer child = new TestWindowContainer(); + mHost.addChild(child, 0); + + final float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, child, alpha); + SurfaceControl dimLayer = getDimLayer(); + mDimmer.resetDimStates(); + + mDimmer.updateDims(mTransaction, new Rect()); + verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( + SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean()); + verify(dimLayer).destroy(); + } + + @Test + public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() throws Exception { + TestWindowContainer child = new TestWindowContainer(); + mHost.addChild(child, 0); + + final float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, child, alpha); + SurfaceControl dimLayer = getDimLayer(); + mDimmer.resetDimStates(); + mDimmer.dimAbove(mTransaction, child, alpha); + + mDimmer.updateDims(mTransaction, new Rect()); + verify(mTransaction).show(dimLayer); + verify(dimLayer, never()).destroy(); + } + + @Test + public void testDimUpdateWhileDimming() throws Exception { + Rect bounds = new Rect(); + TestWindowContainer child = new TestWindowContainer(); + mHost.addChild(child, 0); + + final float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, child, alpha); + + SurfaceControl dimLayer = getDimLayer(); + bounds.set(0, 0, 10, 10); + mDimmer.updateDims(mTransaction, bounds); + verify(mTransaction, times(1)).show(dimLayer); + verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height()); + verify(mTransaction).setPosition(dimLayer, 0, 0); + + bounds.set(10, 10, 30, 30); + mDimmer.updateDims(mTransaction, bounds); + verify(mTransaction).setSize(dimLayer, bounds.width(), bounds.height()); + verify(mTransaction).setPosition(dimLayer, 10, 10); + } + + @Test + public void testRemoveDimImmediately() throws Exception { + TestWindowContainer child = new TestWindowContainer(); + mHost.addChild(child, 0); + + mDimmer.dimAbove(mTransaction, child, 1); + SurfaceControl dimLayer = getDimLayer(); + mDimmer.updateDims(mTransaction, new Rect()); + verify(mTransaction, times(1)).show(dimLayer); + + reset(mSurfaceAnimatorStarter); + mDimmer.dontAnimateExit(); + mDimmer.resetDimStates(); + mDimmer.updateDims(mTransaction, new Rect()); + verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( + SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean()); + verify(mTransaction).destroy(dimLayer); + } + + private SurfaceControl getDimLayer() { + return mDimmer.mDimState.mDimLayer; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java new file mode 100644 index 000000000000..a9744ea2dc1a --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.DisplayCutout.fromBoundingRect; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; + +import static com.android.server.wm.WindowContainer.POSITION_TOP; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.annotation.SuppressLint; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.DisplayMetrics; +import android.util.SparseIntArray; +import android.view.DisplayCutout; +import android.view.MotionEvent; +import android.view.Surface; + +import com.android.server.wm.utils.WmDisplayCutout; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Tests for the {@link DisplayContent} class. + * + * Build/Install/Run: + * atest WmTests:DisplayContentTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class DisplayContentTests extends WindowTestsBase { + + @Test + @FlakyTest(bugId = 77772044) + public void testForAllWindows() throws Exception { + final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, + mDisplayContent, "exiting app"); + final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken; + exitingAppToken.mIsExiting = true; + exitingAppToken.getTask().mStack.mExitingAppTokens.add(exitingAppToken); + + assertForAllWindowsOrder(Arrays.asList( + mWallpaperWindow, + exitingAppWindow, + mChildAppWindowBelow, + mAppWindow, + mChildAppWindowAbove, + mDockedDividerWindow, + mStatusBarWindow, + mNavBarWindow, + mImeWindow, + mImeDialogWindow)); + } + + @Test + public void testForAllWindows_WithAppImeTarget() throws Exception { + final WindowState imeAppTarget = + createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + + sWm.mInputMethodTarget = imeAppTarget; + + assertForAllWindowsOrder(Arrays.asList( + mWallpaperWindow, + mChildAppWindowBelow, + mAppWindow, + mChildAppWindowAbove, + imeAppTarget, + mImeWindow, + mImeDialogWindow, + mDockedDividerWindow, + mStatusBarWindow, + mNavBarWindow)); + } + + @Test + public void testForAllWindows_WithChildWindowImeTarget() throws Exception { + sWm.mInputMethodTarget = mChildAppWindowAbove; + + assertForAllWindowsOrder(Arrays.asList( + mWallpaperWindow, + mChildAppWindowBelow, + mAppWindow, + mChildAppWindowAbove, + mImeWindow, + mImeDialogWindow, + mDockedDividerWindow, + mStatusBarWindow, + mNavBarWindow)); + } + + @Test + public void testForAllWindows_WithStatusBarImeTarget() throws Exception { + sWm.mInputMethodTarget = mStatusBarWindow; + + assertForAllWindowsOrder(Arrays.asList( + mWallpaperWindow, + mChildAppWindowBelow, + mAppWindow, + mChildAppWindowAbove, + mDockedDividerWindow, + mStatusBarWindow, + mImeWindow, + mImeDialogWindow, + mNavBarWindow)); + } + + @Test + public void testForAllWindows_WithInBetweenWindowToken() throws Exception { + // This window is set-up to be z-ordered between some windows that go in the same token like + // the nav bar and status bar. + final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION, + mDisplayContent, "voiceInteractionWindow"); + + assertForAllWindowsOrder(Arrays.asList( + mWallpaperWindow, + mChildAppWindowBelow, + mAppWindow, + mChildAppWindowAbove, + mDockedDividerWindow, + voiceInteractionWindow, + mStatusBarWindow, + mNavBarWindow, + mImeWindow, + mImeDialogWindow)); + } + + @Test + public void testComputeImeTarget() throws Exception { + // Verify that an app window can be an ime target. + final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + appWin.setHasSurface(true); + assertTrue(appWin.canBeImeTarget()); + WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); + assertEquals(appWin, imeTarget); + appWin.mHidden = false; + + // Verify that an child window can be an ime target. + final WindowState childWin = createWindow(appWin, + TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); + childWin.setHasSurface(true); + assertTrue(childWin.canBeImeTarget()); + imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); + assertEquals(childWin, imeTarget); + } + + /** + * This tests stack movement between displays and proper stack's, task's and app token's display + * container references updates. + */ + @Test + public void testMoveStackBetweenDisplays() throws Exception { + // Create a second display. + final DisplayContent dc = createNewDisplay(); + + // Add stack with activity. + final TaskStack stack = createTaskStackOnDisplay(dc); + assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId()); + assertEquals(dc, stack.getParent().getParent()); + assertEquals(dc, stack.getDisplayContent()); + + final Task task = createTaskInStack(stack, 0 /* userId */); + final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken( + dc); + task.addChild(token, 0); + assertEquals(dc, task.getDisplayContent()); + assertEquals(dc, token.getDisplayContent()); + + // Move stack to first display. + mDisplayContent.moveStackToDisplay(stack, true /* onTop */); + assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); + assertEquals(mDisplayContent, stack.getParent().getParent()); + assertEquals(mDisplayContent, stack.getDisplayContent()); + assertEquals(mDisplayContent, task.getDisplayContent()); + assertEquals(mDisplayContent, token.getDisplayContent()); + } + + /** + * This tests override configuration updates for display content. + */ + @Test + public void testDisplayOverrideConfigUpdate() throws Exception { + final int displayId = mDisplayContent.getDisplayId(); + final Configuration currentOverrideConfig = mDisplayContent.getOverrideConfiguration(); + + // Create new, slightly changed override configuration and apply it to the display. + final Configuration newOverrideConfig = new Configuration(currentOverrideConfig); + newOverrideConfig.densityDpi += 120; + newOverrideConfig.fontScale += 0.3; + + sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, displayId); + + // Check that override config is applied. + assertEquals(newOverrideConfig, mDisplayContent.getOverrideConfiguration()); + } + + /** + * This tests global configuration updates when default display config is updated. + */ + @Test + public void testDefaultDisplayOverrideConfigUpdate() throws Exception { + final Configuration currentConfig = mDisplayContent.getConfiguration(); + + // Create new, slightly changed override configuration and apply it to the display. + final Configuration newOverrideConfig = new Configuration(currentConfig); + newOverrideConfig.densityDpi += 120; + newOverrideConfig.fontScale += 0.3; + + sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, DEFAULT_DISPLAY); + + // Check that global configuration is updated, as we've updated default display's config. + Configuration globalConfig = sWm.mRoot.getConfiguration(); + assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi); + assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); + + // Return back to original values. + sWm.setNewDisplayOverrideConfiguration(currentConfig, DEFAULT_DISPLAY); + globalConfig = sWm.mRoot.getConfiguration(); + assertEquals(currentConfig.densityDpi, globalConfig.densityDpi); + assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); + } + + /** + * Tests tapping on a stack in different display results in window gaining focus. + */ + @Test + public void testInputEventBringsCorrectDisplayInFocus() throws Exception { + DisplayContent dc0 = sWm.getDefaultDisplayContentLocked(); + // Create a second display + final DisplayContent dc1 = createNewDisplay(); + + // Add stack with activity. + final TaskStack stack0 = createTaskStackOnDisplay(dc0); + final Task task0 = createTaskInStack(stack0, 0 /* userId */); + final WindowTestUtils.TestAppWindowToken token = + WindowTestUtils.createTestAppWindowToken(dc0); + task0.addChild(token, 0); + dc0.mTapDetector = new TaskTapPointerEventListener(sWm, dc0); + sWm.registerPointerEventListener(dc0.mTapDetector); + final TaskStack stack1 = createTaskStackOnDisplay(dc1); + final Task task1 = createTaskInStack(stack1, 0 /* userId */); + final WindowTestUtils.TestAppWindowToken token1 = + WindowTestUtils.createTestAppWindowToken(dc0); + task1.addChild(token1, 0); + dc1.mTapDetector = new TaskTapPointerEventListener(sWm, dc0); + sWm.registerPointerEventListener(dc1.mTapDetector); + + // tap on primary display (by sending ACTION_DOWN followed by ACTION_UP) + DisplayMetrics dm0 = dc0.getDisplayMetrics(); + dc0.mTapDetector.onPointerEvent( + createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, true)); + dc0.mTapDetector.onPointerEvent( + createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false)); + + // Check focus is on primary display. + assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow()); + + // Tap on secondary display + DisplayMetrics dm1 = dc1.getDisplayMetrics(); + dc1.mTapDetector.onPointerEvent( + createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, true)); + dc1.mTapDetector.onPointerEvent( + createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false)); + + // Check focus is on secondary. + assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow()); + } + + @Test + public void testFocusedWindowMultipleDisplays() throws Exception { + // Create a focusable window and check that focus is calculated correctly + final WindowState window1 = + createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); + assertEquals(window1, sWm.mRoot.computeFocusedWindow()); + + // Check that a new display doesn't affect focus + final DisplayContent dc = createNewDisplay(); + assertEquals(window1, sWm.mRoot.computeFocusedWindow()); + + // Add a window to the second display, and it should be focused + final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2"); + assertEquals(window2, sWm.mRoot.computeFocusedWindow()); + + // Move the first window to the to including parents, and make sure focus is updated + window1.getParent().positionChildAt(POSITION_TOP, window1, true); + assertEquals(window1, sWm.mRoot.computeFocusedWindow()); + } + + @Test + public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { + final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, + sWm.getDefaultDisplayContentLocked(), "keyguard"); + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + + // Add a window to a second display, and it should be focused + final DisplayContent dc = createNewDisplay(); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); + assertEquals(win, sWm.mRoot.computeFocusedWindow()); + + mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true; + assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); + } + + /** + * This tests setting the maximum ui width on a display. + */ + @Test + public void testMaxUiWidth() throws Exception { + final int baseWidth = 1440; + final int baseHeight = 2560; + final int baseDensity = 300; + + mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); + + final int maxWidth = 300; + final int resultingHeight = (maxWidth * baseHeight) / baseWidth; + final int resultingDensity = (maxWidth * baseDensity) / baseWidth; + + mDisplayContent.setMaxUiWidth(maxWidth); + verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity); + + // Assert setting values again does not change; + mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); + verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity); + + final int smallerWidth = 200; + final int smallerHeight = 400; + final int smallerDensity = 100; + + // Specify smaller dimension, verify that it is honored + mDisplayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity); + verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity); + + // Verify that setting the max width to a greater value than the base width has no effect + mDisplayContent.setMaxUiWidth(maxWidth); + verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity); + } + + /** + * This test enforces that the pinned stack is always kept as the top stack. + */ + @Test + public void testPinnedStackLocation() { + final TaskStack pinnedStack = createStackControllerOnStackOnDisplay( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; + // Ensure that the pinned stack is the top stack + assertEquals(pinnedStack, mDisplayContent.getPinnedStack()); + assertEquals(pinnedStack, mDisplayContent.getTopStack()); + // By default, this should try to create a new stack on top + final TaskStack otherStack = createTaskStackOnDisplay(mDisplayContent); + // Ensure that the other stack is on the display. + assertEquals(mDisplayContent, otherStack.getDisplayContent()); + // Ensure that the pinned stack is still on top + assertEquals(pinnedStack, mDisplayContent.getTopStack()); + } + + /** + * Test that WM does not report displays to AM that are pending to be removed. + */ + @Test + public void testDontReportDeferredRemoval() { + // Create a display and add an animating window to it. + final DisplayContent dc = createNewDisplay(); + final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + window.mAnimatingExit = true; + // Request display removal, it should be deferred. + dc.removeIfPossible(); + // Request ordered display ids from WM. + final SparseIntArray orderedDisplayIds = new SparseIntArray(); + sWm.getDisplaysInFocusOrder(orderedDisplayIds); + // Make sure that display that is marked for removal is not reported. + assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId())); + } + + @Test + public void testDisplayCutout_rot0() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + final DisplayContent dc = createNewDisplay(); + dc.mInitialDisplayWidth = 200; + dc.mInitialDisplayHeight = 400; + Rect r = new Rect(80, 0, 120, 10); + final DisplayCutout cutout = new WmDisplayCutout( + fromBoundingRect(r.left, r.top, r.right, r.bottom), null) + .computeSafeInsets(200, 400).getDisplayCutout(); + + dc.mInitialDisplayCutout = cutout; + dc.setRotation(Surface.ROTATION_0); + dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. + + assertEquals(cutout, dc.getDisplayInfo().displayCutout); + } + } + + @Test + public void testDisplayCutout_rot90() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + final DisplayContent dc = createNewDisplay(); + dc.mInitialDisplayWidth = 200; + dc.mInitialDisplayHeight = 400; + Rect r1 = new Rect(80, 0, 120, 10); + final DisplayCutout cutout = new WmDisplayCutout( + fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null) + .computeSafeInsets(200, 400).getDisplayCutout(); + + dc.mInitialDisplayCutout = cutout; + dc.setRotation(Surface.ROTATION_90); + dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. + + final Rect r = new Rect(0, 80, 10, 120); + assertEquals(new WmDisplayCutout( + fromBoundingRect(r.left, r.top, r.right, r.bottom), null) + .computeSafeInsets(400, 200).getDisplayCutout(), dc.getDisplayInfo().displayCutout); + } + } + + @Test + public void testLayoutSeq_assignedDuringLayout() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + + final DisplayContent dc = createNewDisplay(); + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + + dc.setLayoutNeeded(); + dc.performLayout(true /* initial */, false /* updateImeWindows */); + + assertThat(win.mLayoutSeq, is(dc.mLayoutSeq)); + } + } + + @Test + @SuppressLint("InlinedApi") + public void testOrientationDefinedByKeyguard() { + final DisplayContent dc = createNewDisplay(); + // Create a window that requests landscape orientation. It will define device orientation + // by default. + final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); + window.mAppToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, dc, "keyguard"); + keyguard.mHasSurface = true; + keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + + assertEquals("Screen orientation must be defined by the app window by default", + SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); + + keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; + assertEquals("Visible keyguard must influence device orientation", + SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation()); + + sWm.setKeyguardGoingAway(true); + assertEquals("Keyguard that is going away must not influence device orientation", + SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); + } + + @Test + public void testDisableDisplayInfoOverrideFromWindowManager() { + final DisplayContent dc = createNewDisplay(); + + assertTrue(dc.mShouldOverrideDisplayConfiguration); + sWm.dontOverrideDisplayInfo(dc.getDisplayId()); + + assertFalse(dc.mShouldOverrideDisplayConfiguration); + verify(sWm.mDisplayManagerInternal, times(1)) + .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null); + } + + private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth, + int expectedBaseHeight, int expectedBaseDensity) { + assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth); + assertEquals(displayContent.mBaseDisplayHeight, expectedBaseHeight); + assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity); + } + + private void assertForAllWindowsOrder(List expectedWindowsBottomToTop) { + final LinkedList actualWindows = new LinkedList<>(); + + // Test forward traversal. + mDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */); + assertThat("bottomToTop", actualWindows, is(expectedWindowsBottomToTop)); + + actualWindows.clear(); + + // Test backward traversal. + mDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */); + assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop))); + } + + private static List reverseList(List list) { + final ArrayList result = new ArrayList<>(list); + Collections.reverse(result); + return result; + } + + private MotionEvent createTapEvent(float x, float y, boolean isDownEvent) { + final long downTime = SystemClock.uptimeMillis(); + final long eventTime = SystemClock.uptimeMillis() + 100; + final int metaState = 0; + + return MotionEvent.obtain( + downTime, + eventTime, + isDownEvent ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, + x, + y, + metaState); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java new file mode 100644 index 000000000000..68b09982be98 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ClipData; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManagerInternal; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.InputChannel; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.view.View; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Tests for the {@link DragDropController} class. + * + * Build/Install/Run: + * atest WmTests:DragDropControllerTests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@Presubmit +public class DragDropControllerTests extends WindowTestsBase { + private static final int TIMEOUT_MS = 3000; + private TestDragDropController mTarget; + private WindowState mWindow; + private IBinder mToken; + + static class TestDragDropController extends DragDropController { + @GuardedBy("sWm.mWindowMap") + private Runnable mCloseCallback; + + TestDragDropController(WindowManagerService service, Looper looper) { + super(service, looper); + } + + void setOnClosedCallbackLocked(Runnable runnable) { + assertTrue(dragDropActiveLocked()); + mCloseCallback = runnable; + } + + @Override + void onDragStateClosedLocked(DragState dragState) { + super.onDragStateClosedLocked(dragState); + if (mCloseCallback != null) { + mCloseCallback.run(); + mCloseCallback = null; + } + } + } + + /** + * Creates a window state which can be used as a drop target. + */ + private WindowState createDropTargetWindow(String name, int ownerId) { + final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken( + mDisplayContent); + final TaskStack stack = createStackControllerOnStackOnDisplay( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; + final Task task = createTaskInStack(stack, ownerId); + task.addChild(token, 0); + + final WindowState window = createWindow( + null, TYPE_BASE_APPLICATION, token, name, ownerId, false); + window.mInputChannel = new InputChannel(); + window.mHasSurface = true; + return window; + } + + @Before + public void setUp() throws Exception { + final UserManagerInternal userManager = mock(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, userManager); + + super.setUp(); + + mTarget = new TestDragDropController(sWm, sWm.mH.getLooper()); + mDisplayContent = spy(mDisplayContent); + mWindow = createDropTargetWindow("Drag test window", 0); + when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow); + when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); + + synchronized (sWm.mWindowMap) { + sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); + } + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(UserManagerInternal.class); + final CountDownLatch latch; + synchronized (sWm.mWindowMap) { + if (!mTarget.dragDropActiveLocked()) { + return; + } + if (mToken != null) { + mTarget.cancelDragAndDrop(mToken); + } + latch = new CountDownLatch(1); + mTarget.setOnClosedCallbackLocked(() -> { + latch.countDown(); + }); + } + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void testDragFlow() throws Exception { + dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0); + } + + @Test + public void testPerformDrag_NullDataWithGrantUri() throws Exception { + dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); + } + + @Test + public void testPerformDrag_NullDataToOtherUser() throws Exception { + final WindowState otherUsersWindow = + createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE); + when(mDisplayContent.getTouchableWinAtPointLocked(10, 10)) + .thenReturn(otherUsersWindow); + + dragFlow(0, null, 10, 10); + } + + private void dragFlow(int flag, ClipData data, float dropX, float dropY) { + final SurfaceSession appSession = new SurfaceSession(); + try { + final SurfaceControl surface = new SurfaceControl.Builder(appSession) + .setName("drag surface") + .setSize(100, 100) + .setFormat(PixelFormat.TRANSLUCENT) + .build(); + + assertTrue(sWm.mInputManager.transferTouchFocus(null, null)); + mToken = mTarget.performDrag( + new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, + data); + assertNotNull(mToken); + + mTarget.handleMotionEvent(false, dropX, dropY); + mToken = mWindow.mClient.asBinder(); + } finally { + appSession.kill(); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DummyWmTests.java b/services/tests/wmtests/src/com/android/server/wm/DummyWmTests.java deleted file mode 100644 index aecb2783badd..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/DummyWmTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Test; - -import androidx.test.filters.FlakyTest; - -/** - * Dummy test for com.android.server.wm - * TODO(b/113800711): Remove this class once the actual tests are moved from servicestests. - */ -public class DummyWmTests { - - @Presubmit - @Test - public void preSubmitTest() {} - - @FlakyTest - @Presubmit - @Test - public void flakyPreSubmitTest() {} - - @Test - public void postSubmitTest() {} - - @FlakyTest - @Test - public void flakyPostSubmitTest() {} -} diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java new file mode 100644 index 000000000000..f0faf28fd129 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java @@ -0,0 +1,69 @@ +package com.android.server.wm; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.IPinnedStackListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link PinnedStackController}. + * + * Build/Install/Run: + * atest WmTests:PinnedStackControllerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class PinnedStackControllerTest extends WindowTestsBase { + + @Mock private IPinnedStackListener mIPinnedStackListener; + @Mock private IPinnedStackListener.Stub mIPinnedStackListenerStub; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + when(mIPinnedStackListener.asBinder()).thenReturn(mIPinnedStackListenerStub); + } + + @Test + public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException { + sWm.mSupportsPictureInPicture = true; + sWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener); + + verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0); + verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0); + verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false), + eq(false), anyInt()); + verify(mIPinnedStackListener).onActionsChanged(any()); + verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean()); + + reset(mIPinnedStackListener); + + final int SHELF_HEIGHT = 300; + + sWm.setShelfHeight(true, SHELF_HEIGHT); + verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT); + verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false), + eq(true), anyInt()); + verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java new file mode 100644 index 000000000000..fdebaa6df18c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; +import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.os.Binder; +import android.os.IInterface; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.IRecentsAnimationRunner; +import android.view.SurfaceControl; + +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link RecentsAnimationController}. + * + * Build/Install/Run: + * atest WmTests:RecentsAnimationControllerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RecentsAnimationControllerTest extends WindowTestsBase { + + @Mock SurfaceControl mMockLeash; + @Mock SurfaceControl.Transaction mMockTransaction; + @Mock OnAnimationFinishedCallback mFinishedCallback; + @Mock IRecentsAnimationRunner mMockRunner; + @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks; + private RecentsAnimationController mController; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + when(mMockRunner.asBinder()).thenReturn(new Binder()); + mController = new RecentsAnimationController(sWm, mMockRunner, mAnimationCallbacks, + DEFAULT_DISPLAY); + } + + @Test + public void testRemovedBeforeStarted_expectCanceled() throws Exception { + final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + AnimationAdapter adapter = mController.addAnimation(appWindow.getTask(), + false /* isRecentTaskInvisible */); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + + // Remove the app window so that the animation target can not be created + appWindow.removeImmediately(); + mController.startAnimation(); + + // Verify that the finish callback to reparent the leash is called + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + // Verify the animation canceled callback to the app was made + verify(mMockRunner).onAnimationCanceled(); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + } + + @Test + public void testCancelAfterRemove_expectIgnored() throws Exception { + final AppWindowToken appWindow = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + AnimationAdapter adapter = mController.addAnimation(appWindow.getTask(), + false /* isRecentTaskInvisible */); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + + // Remove the app window so that the animation target can not be created + appWindow.removeImmediately(); + mController.startAnimation(); + mController.cleanupAnimation(REORDER_KEEP_IN_PLACE); + try { + mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test"); + } catch (Exception e) { + fail("Unexpected failure when canceling animation after finishing it"); + } + } + + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { + verify(binder, atLeast(0)).asBinder(); + verifyNoMoreInteractions(binder); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java new file mode 100644 index 000000000000..383e53fafeea --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Binder; +import android.os.IInterface; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link RemoteAnimationController}. + * + * Build/Install/Run: + * atest WmTests:RemoteAnimationControllerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RemoteAnimationControllerTest extends WindowTestsBase { + + @Mock SurfaceControl mMockLeash; + @Mock Transaction mMockTransaction; + @Mock OnAnimationFinishedCallback mFinishedCallback; + @Mock IRemoteAnimationRunner mMockRunner; + private RemoteAnimationAdapter mAdapter; + private RemoteAnimationController mController; + private final OffsettableClock mClock = new OffsettableClock.Stopped(); + private TestHandler mHandler; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + when(mMockRunner.asBinder()).thenReturn(new Binder()); + mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50); + mAdapter.setCallingPid(123); + sWm.mH.runWithScissors(() -> { + mHandler = new TestHandler(null, mClock); + }, 0); + mController = new RemoteAnimationController(sWm, mAdapter, mHandler); + } + + @Test + public void testRun() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + sWm.mOpeningApps.add(win.mAppToken); + try { + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + sWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + final RemoteAnimationTarget app = appsCaptor.getValue()[0]; + assertEquals(new Point(50, 100), app.position); + assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds); + assertEquals(win.mAppToken.getPrefixOrderIndex(), app.prefixOrderIndex); + assertEquals(win.mAppToken.getTask().mTaskId, app.taskId); + assertEquals(mMockLeash, app.leash); + assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect); + assertEquals(false, app.isTranslucent); + verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex); + verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y); + verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 100, 50)); + + finishedCaptor.getValue().onAnimationFinished(); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } finally { + sWm.mOpeningApps.clear(); + } + } + + @Test + public void testCancel() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + adapter.onAnimationCancelled(mMockLeash); + verify(mMockRunner).onAnimationCancelled(); + } + + @Test + public void testTimeout() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + mClock.fastForward(2500); + mHandler.timeAdvance(); + + verify(mMockRunner).onAnimationCancelled(); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } + + @Test + public void testTimeout_scaled() throws Exception { + sWm.setAnimationScale(2, 5.0f); + try{ + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + + mClock.fastForward(2500); + mHandler.timeAdvance(); + + verify(mMockRunner, never()).onAnimationCancelled(); + + mClock.fastForward(10000); + mHandler.timeAdvance(); + + verify(mMockRunner).onAnimationCancelled(); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } finally { + sWm.setAnimationScale(2, 1.0f); + } + + } + + @Test + public void testZeroAnimations() throws Exception { + mController.goodToGo(); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + } + + @Test + public void testNotReallyStarted() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + mController.goodToGo(); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + } + + @Test + public void testOneNotStarted() throws Exception { + final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); + final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); + mController.createAnimationAdapter(win1.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + mController.goodToGo(); + sWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + final ArgumentCaptor appsCaptor = + ArgumentCaptor.forClass(RemoteAnimationTarget[].class); + final ArgumentCaptor finishedCaptor = + ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class); + verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture()); + assertEquals(1, appsCaptor.getValue().length); + assertEquals(mMockLeash, appsCaptor.getValue()[0].leash); + } + + @Test + public void testRemovedBeforeStarted() throws Exception { + final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); + final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken, + new Point(50, 100), new Rect(50, 100, 150, 150)); + adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback); + win.mAppToken.removeImmediately(); + mController.goodToGo(); + verifyNoMoreInteractionsExceptAsBinder(mMockRunner); + verify(mFinishedCallback).onAnimationFinished(eq(adapter)); + } + + private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { + verify(binder, atLeast(0)).asBinder(); + verifyNoMoreInteractions(binder); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java new file mode 100644 index 000000000000..317f36cc2077 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -0,0 +1,51 @@ +package com.android.server.wm; + +import static org.junit.Assert.assertTrue; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link RootWindowContainer} class. + * + * Build/Install/Run: + * atest WmTests:RootWindowContainerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RootWindowContainerTests extends WindowTestsBase { + @Test + public void testSetDisplayOverrideConfigurationIfNeeded() throws Exception { + synchronized (sWm.mWindowMap) { + // Add first stack we expect to be updated with configuration change. + final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); + stack.getOverrideConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 5, 5)); + + // Add second task that will be set for deferred removal that should not be returned + // with the configuration change. + final TaskStack deferredDeletedStack = createTaskStackOnDisplay(mDisplayContent); + deferredDeletedStack.getOverrideConfiguration().windowConfiguration.setBounds( + new Rect(0, 0, 5, 5)); + deferredDeletedStack.mDeferRemoval = true; + + final Configuration override = new Configuration( + mDisplayContent.getOverrideConfiguration()); + override.windowConfiguration.setBounds(new Rect(0, 0, 10, 10)); + + // Set display override. + final int[] results = sWm.mRoot.setDisplayOverrideConfigurationIfNeeded(override, + mDisplayContent.getDisplayId()); + + // Ensure only first stack is returned. + assertTrue(results.length == 1); + assertTrue(results[0] == stack.mStackId); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java new file mode 100644 index 000000000000..7e7cfed991ba --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.graphics.Color.RED; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.LEFT; +import static android.view.Gravity.RIGHT; +import static android.view.Gravity.TOP; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static org.junit.Assert.assertEquals; + +import android.app.Activity; +import android.app.ActivityOptions; +import android.app.Instrumentation; +import android.content.Context; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.hardware.display.DisplayManager; +import android.hardware.display.VirtualDisplay; +import android.media.ImageReader; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Pair; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.TextView; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.function.BooleanSupplier; + +/** + * Tests for the {@link android.view.WindowManager.LayoutParams#PRIVATE_FLAG_IS_SCREEN_DECOR} flag. + * + * Build/Install/Run: + * atest WmTests:ScreenDecorWindowTests + */ +// TODO: Add test for FLAG_FULLSCREEN which hides the status bar and also other flags. +// TODO: Test non-Activity windows. +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ScreenDecorWindowTests { + + private final Context mContext = InstrumentationRegistry.getTargetContext(); + private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + + private WindowManager mWm; + private ArrayList mWindows = new ArrayList<>(); + + private Activity mTestActivity; + private VirtualDisplay mDisplay; + private ImageReader mImageReader; + + private int mDecorThickness; + private int mHalfDecorThickness; + + @Before + public void setUp() { + final Pair result = createDisplay(); + mDisplay = result.first; + mImageReader = result.second; + final Display display = mDisplay.getDisplay(); + final Context dContext = mContext.createDisplayContext(display); + mWm = dContext.getSystemService(WindowManager.class); + mTestActivity = startActivityOnDisplay(TestActivity.class, display.getDisplayId()); + final Point size = new Point(); + mDisplay.getDisplay().getRealSize(size); + mDecorThickness = Math.min(size.x, size.y) / 3; + mHalfDecorThickness = mDecorThickness / 2; + } + + @After + public void tearDown() { + while (!mWindows.isEmpty()) { + removeWindow(mWindows.get(0)); + } + finishActivity(mTestActivity); + mDisplay.release(); + mImageReader.close(); + } + + @Test + public void testScreenSides() throws Exception { + // Decor on top + final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + + // Decor at the bottom + updateWindow(decorWindow, BOTTOM, MATCH_PARENT, mDecorThickness, 0, 0); + assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mDecorThickness); + + // Decor to the left + updateWindow(decorWindow, LEFT, mDecorThickness, MATCH_PARENT, 0, 0); + assertInsetGreaterOrEqual(mTestActivity, LEFT, mDecorThickness); + + // Decor to the right + updateWindow(decorWindow, RIGHT, mDecorThickness, MATCH_PARENT, 0, 0); + assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness); + } + + @Test + public void testMultipleDecors() throws Exception { + // Test 2 decor windows on-top. + createDecorWindow(TOP, MATCH_PARENT, mHalfDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mHalfDecorThickness); + createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + + // And one at the bottom. + createDecorWindow(BOTTOM, MATCH_PARENT, mHalfDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, BOTTOM, mHalfDecorThickness); + } + + @Test + public void testFlagChange() throws Exception { + WindowInsets initialInsets = getInsets(mTestActivity); + + final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); + assertTopInsetEquals(mTestActivity, mDecorThickness); + + updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, + 0, PRIVATE_FLAG_IS_SCREEN_DECOR); + + // TODO: fix test and re-enable assertion. + // initialInsets was not actually immutable and just updated to the current insets, + // meaning this assertion never actually tested anything. Now that WindowInsets actually is + // immutable, it turns out the test was broken. + // assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); + + updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, + PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR); + assertTopInsetEquals(mTestActivity, mDecorThickness); + } + + @Test + public void testRemoval() throws Exception { + WindowInsets initialInsets = getInsets(mTestActivity); + + final View decorWindow = createDecorWindow(TOP, MATCH_PARENT, mDecorThickness); + assertInsetGreaterOrEqual(mTestActivity, TOP, mDecorThickness); + + removeWindow(decorWindow); + assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); + } + + private View createDecorWindow(int gravity, int width, int height) { + return createWindow("decorWindow", gravity, width, height, RED, + FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR); + } + + private View createWindow(String name, int gravity, int width, int height, int color, int flags, + int privateFlags) { + + final View[] viewHolder = new View[1]; + final int finalFlag = flags + | FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_NOT_TOUCHABLE; + + // Needs to run on the UI thread. + Handler.getMain().runWithScissors(() -> { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + width, height, TYPE_APPLICATION_OVERLAY, finalFlag, PixelFormat.OPAQUE); + lp.gravity = gravity; + lp.privateFlags |= privateFlags; + + final TextView view = new TextView(mContext); + view.setText("ScreenDecorWindowTests - " + name); + view.setBackgroundColor(color); + mWm.addView(view, lp); + mWindows.add(view); + viewHolder[0] = view; + }, 0); + + waitForIdle(); + return viewHolder[0]; + } + + private void updateWindow(View v, int gravity, int width, int height, + int privateFlags, int privateFlagsMask) { + // Needs to run on the UI thread. + Handler.getMain().runWithScissors(() -> { + final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) v.getLayoutParams(); + lp.gravity = gravity; + lp.width = width; + lp.height = height; + setPrivateFlags(lp, privateFlags, privateFlagsMask); + + mWm.updateViewLayout(v, lp); + }, 0); + + waitForIdle(); + } + + private void removeWindow(View v) { + Handler.getMain().runWithScissors(() -> mWm.removeView(v), 0); + mWindows.remove(v); + waitForIdle(); + } + + private WindowInsets getInsets(Activity a) { + return new WindowInsets(a.getWindow().getDecorView().getRootWindowInsets()); + } + + /** + * Set the flags of the window, as per the + * {@link WindowManager.LayoutParams WindowManager.LayoutParams} + * flags. + * + * @param flags The new window flags (see WindowManager.LayoutParams). + * @param mask Which of the window flag bits to modify. + */ + public void setPrivateFlags(WindowManager.LayoutParams lp, int flags, int mask) { + lp.flags = (lp.flags & ~mask) | (flags & mask); + } + + /** + * Asserts the top inset of {@param activity} is equal to {@param expected} waiting as needed. + */ + private void assertTopInsetEquals(Activity activity, int expected) throws Exception { + waitFor(() -> getInsets(activity).getSystemWindowInsetTop() == expected); + assertEquals(expected, getInsets(activity).getSystemWindowInsetTop()); + } + + /** + * Asserts the inset at {@param side} of {@param activity} is equal to {@param expected} + * waiting as needed. + */ + private void assertInsetGreaterOrEqual(Activity activity, int side, int expected) + throws Exception { + waitFor(() -> { + final WindowInsets insets = getInsets(activity); + switch (side) { + case TOP: return insets.getSystemWindowInsetTop() >= expected; + case BOTTOM: return insets.getSystemWindowInsetBottom() >= expected; + case LEFT: return insets.getSystemWindowInsetLeft() >= expected; + case RIGHT: return insets.getSystemWindowInsetRight() >= expected; + default: return true; + } + }); + + final WindowInsets insets = getInsets(activity); + switch (side) { + case TOP: assertGreaterOrEqual(insets.getSystemWindowInsetTop(), expected); break; + case BOTTOM: assertGreaterOrEqual(insets.getSystemWindowInsetBottom(), expected); break; + case LEFT: assertGreaterOrEqual(insets.getSystemWindowInsetLeft(), expected); break; + case RIGHT: assertGreaterOrEqual(insets.getSystemWindowInsetRight(), expected); break; + } + } + + /** Asserts that the first entry is greater than or equal to the second entry. */ + private void assertGreaterOrEqual(int first, int second) throws Exception { + Assert.assertTrue("Excepted " + first + " >= " + second, first >= second); + } + + private void waitFor(BooleanSupplier waitCondition) { + int retriesLeft = 5; + do { + if (waitCondition.getAsBoolean()) { + break; + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // Well I guess we are not waiting... + } + } while (retriesLeft-- > 0); + } + + private void finishActivity(Activity a) { + if (a == null) { + return; + } + a.finish(); + waitForIdle(); + } + + private void waitForIdle() { + mInstrumentation.waitForIdleSync(); + } + + private Activity startActivityOnDisplay(Class cls, int displayId) { + final Intent intent = new Intent(mContext, cls); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK); + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(displayId); + final Activity activity = mInstrumentation.startActivitySync(intent, options.toBundle()); + waitForIdle(); + + assertEquals(displayId, activity.getDisplay().getDisplayId()); + return activity; + } + + private Pair createDisplay() { + final DisplayManager dm = mContext.getSystemService(DisplayManager.class); + final DisplayInfo displayInfo = new DisplayInfo(); + final Display defaultDisplay = dm.getDisplay(DEFAULT_DISPLAY); + defaultDisplay.getDisplayInfo(displayInfo); + final String name = "ScreenDecorWindowTests"; + int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; + + final ImageReader imageReader = ImageReader.newInstance( + displayInfo.logicalWidth, displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2); + + final VirtualDisplay display = dm.createVirtualDisplay(name, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.logicalDensityDpi, imageReader.getSurface(), + flags); + + return Pair.create(display, imageReader); + } + + public static class TestActivity extends Activity { + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java new file mode 100644 index 000000000000..fb2986c0212d --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/StackWindowControllerTests.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link StackWindowController}. + * + * Build/Install/Run: + * atest WmTests:StackWindowControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class StackWindowControllerTests extends WindowTestsBase { + @Test + public void testRemoveContainer() throws Exception { + final StackWindowController stackController = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(stackController); + + final TaskStack stack = stackController.mContainer; + final Task task = taskController.mContainer; + assertNotNull(stack); + assertNotNull(task); + stackController.removeContainer(); + // Assert that the container was removed. + assertNull(stackController.mContainer); + assertNull(taskController.mContainer); + assertNull(stack.getDisplayContent()); + assertNull(task.getDisplayContent()); + assertNull(task.mStack); + } + + @Test + public void testRemoveContainer_deferRemoval() throws Exception { + final StackWindowController stackController = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(stackController); + + final TaskStack stack = stackController.mContainer; + final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; + // Stack removal is deferred if one of its child is animating. + task.setLocalIsAnimating(true); + + stackController.removeContainer(); + // For the case of deferred removal the stack controller will no longer be connected to the + // container, but the task controller will still be connected to the its container until + // the stack window container is removed. + assertNull(stackController.mContainer); + assertNull(stack.getController()); + assertNotNull(taskController.mContainer); + assertNotNull(task.getController()); + + stack.removeImmediately(); + assertNull(taskController.mContainer); + assertNull(task.getController()); + } + + @Test + public void testReparent() throws Exception { + // Create first stack on primary display. + final StackWindowController stack1Controller = + createStackControllerOnDisplay(mDisplayContent); + final TaskStack stack1 = stack1Controller.mContainer; + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); + final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; + task1.mOnDisplayChangedCalled = false; + + // Create second display and put second stack on it. + final DisplayContent dc = createNewDisplay(); + final StackWindowController stack2Controller = + createStackControllerOnDisplay(dc); + final TaskStack stack2 = stack2Controller.mContainer; + + // Reparent + stack1Controller.reparent(dc.getDisplayId(), new Rect(), true /* onTop */); + assertEquals(dc, stack1.getDisplayContent()); + final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); + final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); + assertEquals(stack1PositionInParent, stack2PositionInParent + 1); + assertTrue(task1.mOnDisplayChangedCalled); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java new file mode 100644 index 000000000000..9ea370284fc2 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import android.animation.AnimationHandler.AnimationFrameCallbackProvider; +import android.animation.ValueAnimator; +import android.graphics.Matrix; +import android.graphics.Point; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Choreographer; +import android.view.Choreographer.FrameCallback; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; + +import com.android.server.wm.LocalAnimationAdapter.AnimationSpec; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.CountDownLatch; + +/** + * Test class for {@link SurfaceAnimationRunner}. + * + * Build/Install/Run: + * atest WmTests:SurfaceAnimationRunnerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class SurfaceAnimationRunnerTest extends WindowTestsBase { + + @Mock SurfaceControl mMockSurface; + @Mock Transaction mMockTransaction; + @Mock AnimationSpec mMockAnimationSpec; + @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private SurfaceAnimationRunner mSurfaceAnimationRunner; + private CountDownLatch mFinishCallbackLatch; + + @Before + public void setUp() throws Exception { + super.setUp(); + mFinishCallbackLatch = new CountDownLatch(1); + mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null, + mMockTransaction); + } + + private void finishedCallback() { + mFinishCallbackLatch.countDown(); + } + + @Test + public void testAnimation() throws Exception { + mSurfaceAnimationRunner + .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction, + this::finishedCallback); + + // Ensure that the initial transformation has been applied. + final Matrix m = new Matrix(); + m.setTranslate(-10, 0); + verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any()); + verify(mMockTransaction, atLeastOnce()).setAlpha(eq(mMockSurface), eq(1.0f)); + + mFinishCallbackLatch.await(1, SECONDS); + assertFinishCallbackCalled(); + + m.setTranslate(10, 0); + verify(mMockTransaction, atLeastOnce()).setMatrix(eq(mMockSurface), eq(m), any()); + + // At least 3 times: After initialization, first frame, last frame. + verify(mMockTransaction, atLeast(3)).setAlpha(eq(mMockSurface), eq(1.0f)); + } + + @Test + public void testCancel_notStarted() throws Exception { + mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null, + mMockTransaction); + mSurfaceAnimationRunner + .startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction, + this::finishedCallback); + mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); + waitUntilHandlersIdle(); + assertTrue(mSurfaceAnimationRunner.mPendingAnimations.isEmpty()); + assertFinishCallbackNotCalled(); + } + + @Test + public void testCancel_running() throws Exception { + mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null, + mMockTransaction); + mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface, + mMockTransaction, this::finishedCallback); + waitUntilNextFrame(); + assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); + assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + waitUntilHandlersIdle(); + assertFinishCallbackNotCalled(); + } + + @FlakyTest(bugId = 71719744) + @Test + public void testCancel_sneakyCancelBeforeUpdate() throws Exception { + mSurfaceAnimationRunner = new SurfaceAnimationRunner(null, () -> new ValueAnimator() { + { + setFloatValues(0f, 1f); + } + + @Override + public void addUpdateListener(AnimatorUpdateListener listener) { + super.addUpdateListener(animation -> { + // Sneaky test cancels animation just before applying frame to simulate + // interleaving of multiple threads. Muahahaha + if (animation.getCurrentPlayTime() > 0) { + mSurfaceAnimationRunner.onAnimationCancelled(mMockSurface); + } + listener.onAnimationUpdate(animation); + }); + } + }, mMockTransaction); + when(mMockAnimationSpec.getDuration()).thenReturn(200L); + mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction, + this::finishedCallback); + + // We need to wait for two frames: The first frame starts the animation, the second frame + // actually cancels the animation. + waitUntilNextFrame(); + waitUntilNextFrame(); + assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + verify(mMockAnimationSpec, atLeastOnce()).apply(any(), any(), eq(0L)); + } + + @FlakyTest(bugId = 74780584) + @Test + public void testDeferStartingAnimations() throws Exception { + mSurfaceAnimationRunner.deferStartingAnimations(); + mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface, + mMockTransaction, this::finishedCallback); + waitUntilNextFrame(); + assertTrue(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + mSurfaceAnimationRunner.continueStartingAnimations(); + waitUntilNextFrame(); + assertFalse(mSurfaceAnimationRunner.mRunningAnimations.isEmpty()); + mFinishCallbackLatch.await(1, SECONDS); + assertFinishCallbackCalled(); + } + + private void waitUntilNextFrame() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, + latch::countDown, null /* token */); + latch.await(); + } + + private void assertFinishCallbackCalled() { + assertEquals(0, mFinishCallbackLatch.getCount()); + } + + private void assertFinishCallbackNotCalled() { + assertEquals(1, mFinishCallbackLatch.getCount()); + } + + private AnimationSpec createTranslateAnimation() { + final Animation a = new TranslateAnimation(-10, 10, 0, 0); + a.initialize(0, 0, 0, 0); + a.setDuration(50); + return new WindowAnimationSpec(a, new Point(0, 0), false /* canSkipFirstFrame */); + } + + /** + * Callback provider that doesn't animate at all. + */ + private static final class NoOpFrameCallbackProvider implements AnimationFrameCallbackProvider { + + @Override + public void postFrameCallback(FrameCallback callback) { + } + + @Override + public void postCommitCallback(Runnable runnable) { + } + + @Override + public long getFrameTime() { + return 0; + } + + @Override + public long getFrameDelay() { + return 0; + } + + @Override + public void setFrameDelay(long delay) { + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java new file mode 100644 index 000000000000..66f4b194a5ac --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Builder; +import android.view.SurfaceControl.Transaction; +import android.view.SurfaceSession; + +import com.android.server.wm.SurfaceAnimator.Animatable; +import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link SurfaceAnimatorTest}. + * + * Build/Install/Run: + * atest WmTests:SurfaceAnimatorTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class SurfaceAnimatorTest extends WindowTestsBase { + + @Mock AnimationAdapter mSpec; + @Mock AnimationAdapter mSpec2; + @Mock Transaction mTransaction; + + private SurfaceSession mSession = new SurfaceSession(); + private MyAnimatable mAnimatable; + private MyAnimatable mAnimatable2; + private DeferFinishAnimatable mDeferFinishAnimatable; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mAnimatable = new MyAnimatable(); + mAnimatable2 = new MyAnimatable(); + mDeferFinishAnimatable = new DeferFinishAnimatable(); + } + + @Test + public void testRunAnimation() throws Exception { + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( + OnAnimationFinishedCallback.class); + assertAnimating(mAnimatable); + verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash.getHandle())); + verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + + callbackCaptor.getValue().onAnimationFinished(mSpec); + assertNotAnimating(mAnimatable); + assertTrue(mAnimatable.mFinishedCallbackCalled); + verify(mTransaction).destroy(eq(mAnimatable.mLeash)); + // TODO: Verify reparenting once we use mPendingTransaction to reparent it back + } + + @Test + public void testOverrideAnimation() throws Exception { + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + final SurfaceControl firstLeash = mAnimatable.mLeash; + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec2, true /* hidden */); + + verify(mTransaction).destroy(eq(firstLeash)); + assertFalse(mAnimatable.mFinishedCallbackCalled); + + final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( + OnAnimationFinishedCallback.class); + assertAnimating(mAnimatable); + verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + + // First animation was finished, but this shouldn't cancel the second animation + callbackCaptor.getValue().onAnimationFinished(mSpec); + assertTrue(mAnimatable.mSurfaceAnimator.isAnimating()); + + // Second animation was finished + verify(mSpec2).startAnimation(any(), any(), callbackCaptor.capture()); + callbackCaptor.getValue().onAnimationFinished(mSpec2); + assertNotAnimating(mAnimatable); + assertTrue(mAnimatable.mFinishedCallbackCalled); + } + + @Test + public void testCancelAnimation() throws Exception { + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + assertAnimating(mAnimatable); + mAnimatable.mSurfaceAnimator.cancelAnimation(); + assertNotAnimating(mAnimatable); + verify(mSpec).onAnimationCancelled(any()); + assertTrue(mAnimatable.mFinishedCallbackCalled); + verify(mTransaction).destroy(eq(mAnimatable.mLeash)); + } + + @Test + public void testDelayingAnimationStart() throws Exception { + mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + verifyZeroInteractions(mSpec); + assertAnimating(mAnimatable); + assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed()); + mAnimatable.mSurfaceAnimator.endDelayingAnimationStart(); + verify(mSpec).startAnimation(any(), any(), any()); + } + + @Test + public void testDelayingAnimationStartAndCancelled() throws Exception { + mAnimatable.mSurfaceAnimator.startDelayingAnimationStart(); + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + mAnimatable.mSurfaceAnimator.cancelAnimation(); + verifyZeroInteractions(mSpec); + assertNotAnimating(mAnimatable); + assertTrue(mAnimatable.mFinishedCallbackCalled); + verify(mTransaction).destroy(eq(mAnimatable.mLeash)); + } + + @Test + public void testTransferAnimation() throws Exception { + mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + + final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( + OnAnimationFinishedCallback.class); + verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + final SurfaceControl leash = mAnimatable.mLeash; + + mAnimatable2.mSurfaceAnimator.transferAnimation(mAnimatable.mSurfaceAnimator); + assertNotAnimating(mAnimatable); + assertAnimating(mAnimatable2); + assertEquals(leash, mAnimatable2.mSurfaceAnimator.mLeash); + verify(mTransaction, never()).destroy(eq(leash)); + callbackCaptor.getValue().onAnimationFinished(mSpec); + assertNotAnimating(mAnimatable2); + assertTrue(mAnimatable2.mFinishedCallbackCalled); + verify(mTransaction).destroy(eq(leash)); + } + + @Test + @FlakyTest(detail = "Promote once confirmed non-flaky") + public void testDeferFinish() throws Exception { + + // Start animation + mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, + true /* hidden */); + final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( + OnAnimationFinishedCallback.class); + assertAnimating(mDeferFinishAnimatable); + verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + + // Finish the animation but then make sure we are deferring. + callbackCaptor.getValue().onAnimationFinished(mSpec); + assertAnimating(mDeferFinishAnimatable); + + // Now end defer finishing. + mDeferFinishAnimatable.endDeferFinishCallback.run(); + assertNotAnimating(mAnimatable2); + assertTrue(mDeferFinishAnimatable.mFinishedCallbackCalled); + verify(mTransaction).destroy(eq(mDeferFinishAnimatable.mLeash)); + } + + private void assertAnimating(MyAnimatable animatable) { + assertTrue(animatable.mSurfaceAnimator.isAnimating()); + assertNotNull(animatable.mSurfaceAnimator.getAnimation()); + } + + private void assertNotAnimating(MyAnimatable animatable) { + assertFalse(animatable.mSurfaceAnimator.isAnimating()); + assertNull(animatable.mSurfaceAnimator.getAnimation()); + } + + private class MyAnimatable implements Animatable { + + final SurfaceControl mParent; + final SurfaceControl mSurface; + final SurfaceAnimator mSurfaceAnimator; + SurfaceControl mLeash; + boolean mFinishedCallbackCalled; + + MyAnimatable() { + mParent = sWm.makeSurfaceBuilder(mSession) + .setName("test surface parent") + .setSize(3000, 3000) + .build(); + mSurface = sWm.makeSurfaceBuilder(mSession) + .setName("test surface") + .setSize(1, 1) + .build(); + mFinishedCallbackCalled = false; + mLeash = null; + mSurfaceAnimator = new SurfaceAnimator(this, mFinishedCallback, sWm); + } + + @Override + public Transaction getPendingTransaction() { + return mTransaction; + } + + @Override + public void commitPendingTransaction() { + } + + @Override + public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { + } + + @Override + public void onAnimationLeashDestroyed(Transaction t) { + } + + @Override + public Builder makeAnimationLeash() { + return new SurfaceControl.Builder(mSession) { + + @Override + public SurfaceControl build() { + mLeash = super.build(); + return mLeash; + } + }.setParent(mParent); + } + + @Override + public SurfaceControl getAnimationLeashParent() { + return mParent; + } + + @Override + public SurfaceControl getSurfaceControl() { + return mSurface; + } + + @Override + public SurfaceControl getParentSurfaceControl() { + return mParent; + } + + @Override + public int getSurfaceWidth() { + return 1; + } + + @Override + public int getSurfaceHeight() { + return 1; + } + + private final Runnable mFinishedCallback = () -> mFinishedCallbackCalled = true; + } + + private class DeferFinishAnimatable extends MyAnimatable { + + Runnable endDeferFinishCallback; + + @Override + public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { + this.endDeferFinishCallback = endDeferFinishCallback; + return true; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java new file mode 100644 index 000000000000..ae730895f5d7 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static com.android.server.wm.TaskPositioner.MIN_ASPECT; +import static com.android.server.wm.WindowManagerService.dipToPixel; +import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; +import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link TaskPositioner} class. + * + * Build/Install/Run: + * atest WmTests:TaskPositionerTests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TaskPositionerTests extends WindowTestsBase { + + private final boolean DEBUGGING = false; + private final String TAG = "TaskPositionerTest"; + + private final static int MOUSE_DELTA_X = 5; + private final static int MOUSE_DELTA_Y = 5; + + private int mMinVisibleWidth; + private int mMinVisibleHeight; + private TaskPositioner mPositioner; + + @Before + public void setUp() throws Exception { + super.setUp(); + + TaskPositioner.setFactory(null); + + final Display display = mDisplayContent.getDisplay(); + final DisplayMetrics dm = new DisplayMetrics(); + display.getMetrics(dm); + + // This should be the same calculation as the TaskPositioner uses. + mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm); + mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm); + + mPositioner = TaskPositioner.create(sWm); + mPositioner.register(mDisplayContent); + } + + @Test + public void testOverrideFactory() throws Exception { + final boolean[] created = new boolean[1]; + created[0] = false; + TaskPositioner.setFactory(new TaskPositioner.Factory() { + @Override + public TaskPositioner create(WindowManagerService service) { + created[0] = true; + return null; + } + }); + + assertNull(TaskPositioner.create(sWm)); + assertTrue(created[0]); + } + + /** + * This tests that free resizing will allow to change the orientation as well + * as does some basic tests (e.g. dragging in Y only will keep X stable). + */ + @Test + @Ignore + public void testBasicFreeWindowResizing() throws Exception { + final Rect r = new Rect(100, 220, 700, 520); + final int midY = (r.top + r.bottom) / 2; + + // Start a drag resize starting upper left. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); + assertBoundsEquals(r, mPositioner.getWindowDragBounds()); + + // Drag to a good landscape size. + mPositioner.resizeDrag(0.0f, 0.0f); + assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a good portrait size. + mPositioner.resizeDrag(400.0f, 0.0f); + assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a too small size for the width. + mPositioner.resizeDrag(2000.0f, r.top); + assertBoundsEquals( + new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a too small size for the height. + mPositioner.resizeDrag(r.left, 2000.0f); + assertBoundsEquals( + new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Start a drag resize left and see that only the left coord changes.. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r); + + // Drag to the left. + mPositioner.resizeDrag(0.0f, midY); + assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the right. + mPositioner.resizeDrag(200.0f, midY); + assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the top + mPositioner.resizeDrag(r.left, 0.0f); + assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the bottom + mPositioner.resizeDrag(r.left, 1000.0f); + assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + } + + /** + * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored. + */ + @Test + @Ignore + public void testFreeWindowResizingTestAllEdges() throws Exception { + final Rect r = new Rect(100, 220, 700, 520); + final int midX = (r.left + r.right) / 2; + final int midY = (r.top + r.bottom) / 2; + + // Drag upper left. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); + mPositioner.resizeDrag(0.0f, 0.0f); + assertTrue(r.left != mPositioner.getWindowDragBounds().left); + assertEquals(r.right, mPositioner.getWindowDragBounds().right); + assertTrue(r.top != mPositioner.getWindowDragBounds().top); + assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); + + // Drag upper. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r); + mPositioner.resizeDrag(0.0f, 0.0f); + assertEquals(r.left, mPositioner.getWindowDragBounds().left); + assertEquals(r.right, mPositioner.getWindowDragBounds().right); + assertTrue(r.top != mPositioner.getWindowDragBounds().top); + assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); + + // Drag upper right. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); + mPositioner.resizeDrag(r.right + 100, 0.0f); + assertEquals(r.left, mPositioner.getWindowDragBounds().left); + assertTrue(r.right != mPositioner.getWindowDragBounds().right); + assertTrue(r.top != mPositioner.getWindowDragBounds().top); + assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); + + // Drag right. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, midY, r); + mPositioner.resizeDrag(r.right + 100, 0.0f); + assertEquals(r.left, mPositioner.getWindowDragBounds().left); + assertTrue(r.right != mPositioner.getWindowDragBounds().right); + assertEquals(r.top, mPositioner.getWindowDragBounds().top); + assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); + + // Drag bottom right. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, + r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r); + mPositioner.resizeDrag(r.right + 100, r.bottom + 100); + assertEquals(r.left, mPositioner.getWindowDragBounds().left); + assertTrue(r.right != mPositioner.getWindowDragBounds().right); + assertEquals(r.top, mPositioner.getWindowDragBounds().top); + assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom); + + // Drag bottom. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, midX, r.bottom + MOUSE_DELTA_Y, r); + mPositioner.resizeDrag(r.right + 100, r.bottom + 100); + assertEquals(r.left, mPositioner.getWindowDragBounds().left); + assertEquals(r.right, mPositioner.getWindowDragBounds().right); + assertEquals(r.top, mPositioner.getWindowDragBounds().top); + assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom); + + // Drag bottom left. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r); + mPositioner.resizeDrag(0.0f, r.bottom + 100); + assertTrue(r.left != mPositioner.getWindowDragBounds().left); + assertEquals(r.right, mPositioner.getWindowDragBounds().right); + assertEquals(r.top, mPositioner.getWindowDragBounds().top); + assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom); + + // Drag left. + mPositioner.startDrag(true /*resizing*/, + false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midX, r); + mPositioner.resizeDrag(0.0f, r.bottom + 100); + assertTrue(r.left != mPositioner.getWindowDragBounds().left); + assertEquals(r.right, mPositioner.getWindowDragBounds().right); + assertEquals(r.top, mPositioner.getWindowDragBounds().top); + assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); + } + + /** + * This tests that a constrained landscape window will keep the aspect and do the + * right things upon resizing when dragged from the top left corner. + */ + @Test + @Ignore + public void testLandscapePreservedWindowResizingDragTopLeft() throws Exception { + final Rect r = new Rect(100, 220, 700, 520); + + mPositioner.startDrag(true /*resizing*/, + true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); + assertBoundsEquals(r, mPositioner.getWindowDragBounds()); + + // Drag to a good landscape size. + mPositioner.resizeDrag(0.0f, 0.0f); + assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a good portrait size. + mPositioner.resizeDrag(400.0f, 0.0f); + int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT); + assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a too small size for the width. + mPositioner.resizeDrag(2000.0f, r.top); + final int w = mMinVisibleWidth; + final int h = Math.round(w / MIN_ASPECT); + assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a too small size for the height. + mPositioner.resizeDrag(r.left, 2000.0f); + assertBoundsEquals( + new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + } + + /** + * This tests that a constrained landscape window will keep the aspect and do the + * right things upon resizing when dragged from the left corner. + */ + @Test + @Ignore + public void testLandscapePreservedWindowResizingDragLeft() throws Exception { + final Rect r = new Rect(100, 220, 700, 520); + final int midY = (r.top + r.bottom) / 2; + + mPositioner.startDrag(true /*resizing*/, + true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r); + + // Drag to the left. + mPositioner.resizeDrag(0.0f, midY); + assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the right. + mPositioner.resizeDrag(200.0f, midY); + assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag all the way to the right and see the height also shrinking. + mPositioner.resizeDrag(2000.0f, midY); + final int w = mMinVisibleWidth; + final int h = Math.round((float)w / MIN_ASPECT); + assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), + mPositioner.getWindowDragBounds()); + + // Drag to the top. + mPositioner.resizeDrag(r.left, 0.0f); + assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the bottom. + mPositioner.resizeDrag(r.left, 1000.0f); + assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + } + + /** + * This tests that a constrained landscape window will keep the aspect and do the + * right things upon resizing when dragged from the top corner. + */ + @Test + @Ignore + public void testLandscapePreservedWindowResizingDragTop() throws Exception { + final Rect r = new Rect(100, 220, 700, 520); + final int midX = (r.left + r.right) / 2; + + mPositioner.startDrag(true /*resizing*/, + true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r); + + // Drag to the left (no change). + mPositioner.resizeDrag(0.0f, r.top); + assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the right (no change). + mPositioner.resizeDrag(2000.0f, r.top); + assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the top. + mPositioner.resizeDrag(300.0f, 0.0f); + int h = r.bottom - MOUSE_DELTA_Y; + int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT)); + assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the bottom. + mPositioner.resizeDrag(r.left, 1000.0f); + h = mMinVisibleHeight; + assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + } + + /** + * This tests that a constrained portrait window will keep the aspect and do the + * right things upon resizing when dragged from the top left corner. + */ + @Test + @Ignore + public void testPortraitPreservedWindowResizingDragTopLeft() throws Exception { + final Rect r = new Rect(330, 100, 630, 600); + + mPositioner.startDrag(true /*resizing*/, + true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r); + assertBoundsEquals(r, mPositioner.getWindowDragBounds()); + + // Drag to a good landscape size. + mPositioner.resizeDrag(0.0f, 0.0f); + int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT); + assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a good portrait size. + mPositioner.resizeDrag(500.0f, 0.0f); + assertBoundsEquals(new Rect(500 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to a too small size for the height and the the width shrinking. + mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f); + final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT)); + final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT)); + assertBoundsEquals( + new Rect(r.right - w, r.bottom - h, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + } + + /** + * This tests that a constrained portrait window will keep the aspect and do the + * right things upon resizing when dragged from the left corner. + */ + @Test + @Ignore + public void testPortraitPreservedWindowResizingDragLeft() throws Exception { + final Rect r = new Rect(330, 100, 630, 600); + final int midY = (r.top + r.bottom) / 2; + + mPositioner.startDrag(true /*resizing*/, + true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r); + + // Drag to the left. + mPositioner.resizeDrag(0.0f, midY); + int w = r.right - MOUSE_DELTA_X; + int h = Math.round(w * MIN_ASPECT); + assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h), + mPositioner.getWindowDragBounds()); + + // Drag to the right. + mPositioner.resizeDrag(450.0f, midY); + assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag all the way to the right. + mPositioner.resizeDrag(2000.0f, midY); + w = mMinVisibleWidth; + h = Math.max(Math.round((float)w * MIN_ASPECT), r.height()); + assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), + mPositioner.getWindowDragBounds()); + + // Drag to the top. + mPositioner.resizeDrag(r.left, 0.0f); + assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the bottom. + mPositioner.resizeDrag(r.left, 1000.0f); + assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + } + + /** + * This tests that a constrained portrait window will keep the aspect and do the + * right things upon resizing when dragged from the top corner. + */ + @Test + @Ignore + public void testPortraitPreservedWindowResizingDragTop() throws Exception { + final Rect r = new Rect(330, 100, 630, 600); + final int midX = (r.left + r.right) / 2; + + mPositioner.startDrag(true /*resizing*/, + true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r); + + // Drag to the left (no change). + mPositioner.resizeDrag(0.0f, r.top); + assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the right (no change). + mPositioner.resizeDrag(2000.0f, r.top); + assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the top. + mPositioner.resizeDrag(300.0f, 0.0f); + int h = r.bottom - MOUSE_DELTA_Y; + int w = Math.min(r.width(), Math.round(h / MIN_ASPECT)); + assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), + mPositioner.getWindowDragBounds()); + + // Drag to the bottom. + mPositioner.resizeDrag(r.left, 1000.0f); + h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT)); + w = Math.round(h / MIN_ASPECT); + assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom), + mPositioner.getWindowDragBounds()); + } + + private void assertBoundsEquals(Rect expected, Rect actual) { + if (DEBUGGING) { + if (!expected.equals(actual)) { + Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString() + + ") " + Log.getStackTraceString(new Throwable())); + } + } + assertEquals(expected.left, actual.left); + assertEquals(expected.right, actual.right); + assertEquals(expected.top, actual.top); + assertEquals(expected.bottom, actual.bottom); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java new file mode 100644 index 000000000000..659ec41c30c4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.InputChannel; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link TaskPositioningController} class. + * + * Build/Install/Run: + * atest WmTests:TaskPositioningControllerTests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@Presubmit +public class TaskPositioningControllerTests extends WindowTestsBase { + private static final int TIMEOUT_MS = 1000; + private TaskPositioningController mTarget; + private WindowState mWindow; + + @Before + public void setUp() throws Exception { + super.setUp(); + + assertNotNull(sWm.mTaskPositioningController); + mTarget = sWm.mTaskPositioningController; + + when(sWm.mInputManager.transferTouchFocus( + any(InputChannel.class), + any(InputChannel.class))).thenReturn(true); + + mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); + mWindow.mInputChannel = new InputChannel(); + synchronized (sWm.mWindowMap) { + sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); + } + } + + @Test + public void testStartAndFinishPositioning() throws Exception { + synchronized (sWm.mWindowMap) { + assertFalse(mTarget.isPositioningLocked()); + assertNull(mTarget.getDragWindowHandleLocked()); + } + + assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0)); + + synchronized (sWm.mWindowMap) { + assertTrue(mTarget.isPositioningLocked()); + assertNotNull(mTarget.getDragWindowHandleLocked()); + } + + mTarget.finishTaskPositioning(); + // Wait until the looper processes finishTaskPositioning. + assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS)); + + assertFalse(mTarget.isPositioningLocked()); + assertNull(mTarget.getDragWindowHandleLocked()); + } + + @Test + public void testHandleTapOutsideTask() throws Exception { + synchronized (sWm.mWindowMap) { + + assertFalse(mTarget.isPositioningLocked()); + assertNull(mTarget.getDragWindowHandleLocked()); + } + + final DisplayContent content = mock(DisplayContent.class); + when(content.findTaskForResizePoint(anyInt(), anyInt())).thenReturn(mWindow.getTask()); + assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); + + mTarget.handleTapOutsideTask(content, 0, 0); + // Wait until the looper processes finishTaskPositioning. + assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS)); + + synchronized (sWm.mWindowMap) { + assertTrue(mTarget.isPositioningLocked()); + assertNotNull(mTarget.getDragWindowHandleLocked()); + } + + mTarget.finishTaskPositioning(); + // Wait until the looper processes finishTaskPositioning. + assertTrue(sWm.mH.runWithScissors(() -> {}, TIMEOUT_MS)); + + assertFalse(mTarget.isPositioningLocked()); + assertNull(mTarget.getDragWindowHandleLocked()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java new file mode 100644 index 000000000000..1aefa277e84f --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskSnapshotCache}. + * + * Build/Install/Run: + * atest WmTests:TaskSnapshotCacheTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { + + private TaskSnapshotCache mCache; + + @Before + public void setUp() throws Exception { + super.setUp(); + mCache = new TaskSnapshotCache(sWm, mLoader); + } + + @Test + public void testAppRemoved() throws Exception { + final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + mCache.putSnapshot(window.getTask(), createSnapshot()); + assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, + false /* restoreFromDisk */, false /* reducedResolution */)); + mCache.onAppRemoved(window.mAppToken); + assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, + false /* restoreFromDisk */, false /* reducedResolution */)); + } + + @Test + public void testAppDied() throws Exception { + final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + mCache.putSnapshot(window.getTask(), createSnapshot()); + assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, + false /* restoreFromDisk */, false /* reducedResolution */)); + mCache.onAppDied(window.mAppToken); + assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, + false /* restoreFromDisk */, false /* reducedResolution */)); + } + + @Test + public void testTaskRemoved() throws Exception { + final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + mCache.putSnapshot(window.getTask(), createSnapshot()); + assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, + false /* restoreFromDisk */, false /* reducedResolution */)); + mCache.onTaskRemoved(window.getTask().mTaskId); + assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */, + false /* restoreFromDisk */, false /* reducedResolution */)); + } + + @Test + public void testReduced_notCached() throws Exception { + final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot()); + mPersister.waitForQueueEmpty(); + assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, + false /* restoreFromDisk */, false /* reducedResolution */)); + + // Load it from disk + assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, + true /* restoreFromDisk */, true /* reducedResolution */)); + + // Make sure it's not in the cache now. + assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, + false /* restoreFromDisk */, false /* reducedResolution */)); + } + + @Test + public void testRestoreFromDisk() throws Exception { + final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot()); + mPersister.waitForQueueEmpty(); + assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, + false /* restoreFromDisk */, false /* reducedResolution */)); + + // Load it from disk + assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, + true /* restoreFromDisk */, false /* reducedResolution */)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java new file mode 100644 index 000000000000..77521a652ca3 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.TRANSIT_UNSET; + +import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_APP_THEME; +import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL; + +import static junit.framework.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; + +import com.google.android.collect.Sets; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskSnapshotController}. + * + * Build/Install/Run: + * atest WmTests:TaskSnapshotControllerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskSnapshotControllerTest extends WindowTestsBase { + + @Test + public void testGetClosingApps_closing() throws Exception { + final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, + "closingWindow"); + closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, + true /* performLayout */, false /* isVoiceInteraction */); + final ArraySet closingApps = new ArraySet<>(); + closingApps.add(closingWindow.mAppToken); + final ArraySet closingTasks = new ArraySet<>(); + sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks); + assertEquals(1, closingTasks.size()); + assertEquals(closingWindow.mAppToken.getTask(), closingTasks.valueAt(0)); + } + + @Test + public void testGetClosingApps_notClosing() throws Exception { + final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, + "closingWindow"); + final WindowState openingWindow = createAppWindow(closingWindow.getTask(), + FIRST_APPLICATION_WINDOW, "openingWindow"); + closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, + true /* performLayout */, false /* isVoiceInteraction */); + openingWindow.mAppToken.setVisibility(null, true /* visible */, TRANSIT_UNSET, + true /* performLayout */, false /* isVoiceInteraction */); + final ArraySet closingApps = new ArraySet<>(); + closingApps.add(closingWindow.mAppToken); + final ArraySet closingTasks = new ArraySet<>(); + sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks); + assertEquals(0, closingTasks.size()); + } + + @Test + public void testGetClosingApps_skipClosingAppsSnapshotTasks() throws Exception { + final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, + "closingWindow"); + closingWindow.mAppToken.setVisibility(null, false /* visible */, TRANSIT_UNSET, + true /* performLayout */, false /* isVoiceInteraction */); + final ArraySet closingApps = new ArraySet<>(); + closingApps.add(closingWindow.mAppToken); + final ArraySet closingTasks = new ArraySet<>(); + sWm.mTaskSnapshotController.addSkipClosingAppSnapshotTasks( + Sets.newArraySet(closingWindow.mAppToken.getTask())); + sWm.mTaskSnapshotController.getClosingTasks(closingApps, closingTasks); + assertEquals(0, closingTasks.size()); + } + + @Test + public void testGetSnapshotMode() throws Exception { + final WindowState disabledWindow = createWindow(null, + FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow"); + disabledWindow.mAppToken.setDisablePreviewScreenshots(true); + assertEquals(SNAPSHOT_MODE_APP_THEME, + sWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask())); + + final WindowState normalWindow = createWindow(null, + FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow"); + assertEquals(SNAPSHOT_MODE_REAL, + sWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask())); + + final WindowState secureWindow = createWindow(null, + FIRST_APPLICATION_WINDOW, mDisplayContent, "secureWindow"); + secureWindow.mAttrs.flags |= FLAG_SECURE; + assertEquals(SNAPSHOT_MODE_APP_THEME, + sWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java new file mode 100644 index 000000000000..ed81dafe3205 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.app.ActivityManager.TaskSnapshot; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; +import android.view.View; + +import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader} + * + * Build/Install/Run: + * atest WmTests:TaskSnapshotPersisterLoaderTest + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase { + + private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40); + + @Test + public void testPersistAndLoadSnapshot() { + mPersister.persistSnapshot(1 , mTestUserId, createSnapshot()); + mPersister.waitForQueueEmpty(); + final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"), + new File(sFilesDir.getPath() + "/snapshots/1.jpg"), + new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")}; + assertTrueForFiles(files, File::exists, " must exist"); + final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* reduced */); + assertNotNull(snapshot); + assertEquals(TEST_INSETS, snapshot.getContentInsets()); + assertNotNull(snapshot.getSnapshot()); + assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation()); + } + + private void assertTrueForFiles(File[] files, Predicate predicate, String message) { + for (File file : files) { + assertTrue(file.getName() + message, predicate.apply(file)); + } + } + + @Test + public void testTaskRemovedFromRecents() { + mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); + mPersister.onTaskRemovedFromRecents(1, mTestUserId); + mPersister.waitForQueueEmpty(); + assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists()); + assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists()); + assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists()); + } + + /** + * Tests that persisting a couple of snapshots is being throttled. + */ + @Test + public void testThrottling() { + long ms = SystemClock.elapsedRealtime(); + mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); + mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); + mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); + mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); + mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); + mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); + mPersister.waitForQueueEmpty(); + assertTrue(SystemClock.elapsedRealtime() - ms > 500); + } + + /** + * Tests that too many store write queue items are being purged. + */ + @Test + public void testPurging() { + mPersister.persistSnapshot(100, mTestUserId, createSnapshot()); + mPersister.waitForQueueEmpty(); + mPersister.setPaused(true); + mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); + mPersister.removeObsoleteFiles(new ArraySet<>(), new int[] { mTestUserId }); + mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); + mPersister.persistSnapshot(3, mTestUserId, createSnapshot()); + mPersister.persistSnapshot(4, mTestUserId, createSnapshot()); + mPersister.setPaused(false); + mPersister.waitForQueueEmpty(); + + // Make sure 1,2 were purged but removeObsoleteFiles wasn't. + final File[] existsFiles = new File[] { + new File(sFilesDir.getPath() + "/snapshots/3.proto"), + new File(sFilesDir.getPath() + "/snapshots/4.proto")}; + final File[] nonExistsFiles = new File[] { + new File(sFilesDir.getPath() + "/snapshots/100.proto"), + new File(sFilesDir.getPath() + "/snapshots/1.proto"), + new File(sFilesDir.getPath() + "/snapshots/1.proto")}; + assertTrueForFiles(existsFiles, File::exists, " must exist"); + assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); + } + + @Test + public void testGetTaskId() { + RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem = + mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[] {}); + assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp")); + assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err")); + assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/")); + assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg")); + assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto")); + assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg")); + assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg")); + } + + @Test + public void testLowResolutionPersistAndLoadSnapshot() { + TaskSnapshot a = createSnapshot(0.5f /* reducedResolution */); + assertTrue(a.isReducedResolution()); + mPersister.persistSnapshot(1 , mTestUserId, a); + mPersister.waitForQueueEmpty(); + final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"), + new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")}; + final File[] nonExistsFiles = new File[] { + new File(sFilesDir.getPath() + "/snapshots/1.jpg"), + }; + assertTrueForFiles(files, File::exists, " must exist"); + assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); + final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, true /* reduced */); + assertNotNull(snapshot); + assertEquals(TEST_INSETS, snapshot.getContentInsets()); + assertNotNull(snapshot.getSnapshot()); + assertEquals(Configuration.ORIENTATION_PORTRAIT, snapshot.getOrientation()); + + final TaskSnapshot snapshotNotExist = mLoader.loadTask(1, mTestUserId, false /* reduced */); + assertNull(snapshotNotExist); + } + + @Test + public void testIsRealSnapshotPersistAndLoadSnapshot() { + TaskSnapshot a = new TaskSnapshotBuilder() + .setIsRealSnapshot(true) + .build(); + TaskSnapshot b = new TaskSnapshotBuilder() + .setIsRealSnapshot(false) + .build(); + assertTrue(a.isRealSnapshot()); + assertFalse(b.isRealSnapshot()); + mPersister.persistSnapshot(1, mTestUserId, a); + mPersister.persistSnapshot(2, mTestUserId, b); + mPersister.waitForQueueEmpty(); + final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); + final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); + assertNotNull(snapshotA); + assertNotNull(snapshotB); + assertTrue(snapshotA.isRealSnapshot()); + assertFalse(snapshotB.isRealSnapshot()); + } + + @Test + public void testWindowingModePersistAndLoadSnapshot() { + TaskSnapshot a = new TaskSnapshotBuilder() + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build(); + TaskSnapshot b = new TaskSnapshotBuilder() + .setWindowingMode(WINDOWING_MODE_PINNED) + .build(); + assertTrue(a.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + assertTrue(b.getWindowingMode() == WINDOWING_MODE_PINNED); + mPersister.persistSnapshot(1, mTestUserId, a); + mPersister.persistSnapshot(2, mTestUserId, b); + mPersister.waitForQueueEmpty(); + final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); + final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); + assertNotNull(snapshotA); + assertNotNull(snapshotB); + assertTrue(snapshotA.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + assertTrue(snapshotB.getWindowingMode() == WINDOWING_MODE_PINNED); + } + + @Test + public void testIsTranslucentPersistAndLoadSnapshot() { + TaskSnapshot a = new TaskSnapshotBuilder() + .setIsTranslucent(true) + .build(); + TaskSnapshot b = new TaskSnapshotBuilder() + .setIsTranslucent(false) + .build(); + assertTrue(a.isTranslucent()); + assertFalse(b.isTranslucent()); + mPersister.persistSnapshot(1, mTestUserId, a); + mPersister.persistSnapshot(2, mTestUserId, b); + mPersister.waitForQueueEmpty(); + final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); + final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); + assertNotNull(snapshotA); + assertNotNull(snapshotB); + assertTrue(snapshotA.isTranslucent()); + assertFalse(snapshotB.isTranslucent()); + } + + @Test + public void testSystemUiVisibilityPersistAndLoadSnapshot() { + final int lightBarFlags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + TaskSnapshot a = new TaskSnapshotBuilder() + .setSystemUiVisibility(0) + .build(); + TaskSnapshot b = new TaskSnapshotBuilder() + .setSystemUiVisibility(lightBarFlags) + .build(); + assertTrue(a.getSystemUiVisibility() == 0); + assertTrue(b.getSystemUiVisibility() == lightBarFlags); + mPersister.persistSnapshot(1, mTestUserId, a); + mPersister.persistSnapshot(2, mTestUserId, b); + mPersister.waitForQueueEmpty(); + final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId, false /* reduced */); + final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId, false /* reduced */); + assertNotNull(snapshotA); + assertNotNull(snapshotB); + assertTrue(snapshotA.getSystemUiVisibility() == 0); + assertTrue(snapshotB.getSystemUiVisibility() == lightBarFlags); + } + + @Test + public void testRemoveObsoleteFiles() { + mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); + mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); + final ArraySet taskIds = new ArraySet<>(); + taskIds.add(1); + mPersister.removeObsoleteFiles(taskIds, new int[] { mTestUserId }); + mPersister.waitForQueueEmpty(); + final File[] existsFiles = new File[] { + new File(sFilesDir.getPath() + "/snapshots/1.proto"), + new File(sFilesDir.getPath() + "/snapshots/1.jpg"), + new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") }; + final File[] nonExistsFiles = new File[] { + new File(sFilesDir.getPath() + "/snapshots/2.proto"), + new File(sFilesDir.getPath() + "/snapshots/2.jpg"), + new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")}; + assertTrueForFiles(existsFiles, File::exists, " must exist"); + assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); + } + + @Test + public void testRemoveObsoleteFiles_addedOneInTheMeantime() { + mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); + final ArraySet taskIds = new ArraySet<>(); + taskIds.add(1); + mPersister.removeObsoleteFiles(taskIds, new int[] { mTestUserId }); + mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); + mPersister.waitForQueueEmpty(); + final File[] existsFiles = new File[] { + new File(sFilesDir.getPath() + "/snapshots/1.proto"), + new File(sFilesDir.getPath() + "/snapshots/1.jpg"), + new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"), + new File(sFilesDir.getPath() + "/snapshots/2.proto"), + new File(sFilesDir.getPath() + "/snapshots/2.jpg"), + new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")}; + assertTrueForFiles(existsFiles, File::exists, " must exist"); + } + + /** + * Private predicate definition. + * + * This is needed because com.android.internal.util.Predicate is deprecated + * and can only be used with classes fron android.test.runner. This cannot + * use java.util.function.Predicate because that is not present on all API + * versions that this test must run on. + */ + private interface Predicate { + boolean apply(T t); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java new file mode 100644 index 000000000000..8b8604365fa1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.graphics.GraphicBuffer.USAGE_HW_TEXTURE; +import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY; + +import android.app.ActivityManager.TaskSnapshot; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.GraphicBuffer; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.UserManager; +import android.support.test.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.io.File; + +/** + * Base class for tests that use a {@link TaskSnapshotPersister}. + */ +class TaskSnapshotPersisterTestBase extends WindowTestsBase { + + private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40); + + TaskSnapshotPersister mPersister; + TaskSnapshotLoader mLoader; + int mTestUserId; + static File sFilesDir; + + @BeforeClass + public static void setUpUser() { + sFilesDir = InstrumentationRegistry.getContext().getFilesDir(); + } + + @Before + public void setUp() throws Exception { + super.setUp(); + final UserManager um = UserManager.get(InstrumentationRegistry.getContext()); + mTestUserId = um.getUserHandle(); + mPersister = new TaskSnapshotPersister(userId -> sFilesDir); + mLoader = new TaskSnapshotLoader(mPersister); + mPersister.start(); + } + + @After + public void tearDown() throws Exception { + cleanDirectory(); + } + + private void cleanDirectory() { + final File[] files = new File(sFilesDir, "snapshots").listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (!file.isDirectory()) { + file.delete(); + } + } + } + + TaskSnapshot createSnapshot() { + return createSnapshot(1f /* scale */); + } + + TaskSnapshot createSnapshot(float scale) { + return new TaskSnapshotBuilder() + .setScale(scale) + .build(); + } + + /** + * Builds a TaskSnapshot. + */ + class TaskSnapshotBuilder { + + private float mScale = 1f; + private boolean mIsRealSnapshot = true; + private boolean mIsTranslucent = false; + private int mWindowingMode = WINDOWING_MODE_FULLSCREEN; + private int mSystemUiVisibility = 0; + + public TaskSnapshotBuilder setScale(float scale) { + mScale = scale; + return this; + } + + public TaskSnapshotBuilder setIsRealSnapshot(boolean isRealSnapshot) { + mIsRealSnapshot = isRealSnapshot; + return this; + } + + public TaskSnapshotBuilder setIsTranslucent(boolean isTranslucent) { + mIsTranslucent = isTranslucent; + return this; + } + + public TaskSnapshotBuilder setWindowingMode(int windowingMode) { + mWindowingMode = windowingMode; + return this; + } + + public TaskSnapshotBuilder setSystemUiVisibility(int systemUiVisibility) { + mSystemUiVisibility = systemUiVisibility; + return this; + } + + public TaskSnapshot build() { + final GraphicBuffer buffer = GraphicBuffer.create(100, 100, PixelFormat.RGBA_8888, + USAGE_HW_TEXTURE | USAGE_SW_READ_RARELY | USAGE_SW_READ_RARELY); + Canvas c = buffer.lockCanvas(); + c.drawColor(Color.RED); + buffer.unlockCanvasAndPost(c); + return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS, + mScale < 1f /* reducedResolution */, mScale, mIsRealSnapshot, mWindowingMode, + mSystemUiVisibility, mIsTranslucent); + } + + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java new file mode 100644 index 000000000000..287fff554a61 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager.TaskSnapshot; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.GraphicBuffer; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.Surface; + +import com.android.server.wm.TaskSnapshotSurface.Window; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskSnapshotSurface}. + * + * Build/Install/Run: + * atest WmTests:TaskSnapshotSurfaceTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskSnapshotSurfaceTest extends WindowTestsBase { + + private TaskSnapshotSurface mSurface; + + private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis, + int windowFlags, Rect taskBounds) { + final GraphicBuffer buffer = GraphicBuffer.create(width, height, PixelFormat.RGBA_8888, + GraphicBuffer.USAGE_SW_READ_NEVER | GraphicBuffer.USAGE_SW_WRITE_NEVER); + final TaskSnapshot snapshot = new TaskSnapshot(buffer, + ORIENTATION_PORTRAIT, contentInsets, false, 1.0f, true /* isRealSnapshot */, + WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */); + mSurface = new TaskSnapshotSurface(sWm, new Window(), new Surface(), snapshot, "Test", + Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds, + ORIENTATION_PORTRAIT); + } + + private void setupSurface(int width, int height) { + setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, width, height)); + } + + @Test + public void fillEmptyBackground_fillHorizontally() throws Exception { + setupSurface(200, 100); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(200); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); + verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); + } + + @Test + public void fillEmptyBackground_fillVertically() throws Exception { + setupSurface(100, 200); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(200); + mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); + verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); + } + + @Test + public void fillEmptyBackground_fillBoth() throws Exception { + setupSurface(200, 200); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(200); + when(mockCanvas.getHeight()).thenReturn(200); + mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); + verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); + verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any()); + } + + @Test + public void fillEmptyBackground_dontFill_sameSize() throws Exception { + setupSurface(100, 100); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception { + setupSurface(100, 100); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void testCalculateSnapshotCrop() { + setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 100, 90), mSurface.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_taskNotOnTop() { + setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 100)); + assertEquals(new Rect(0, 10, 100, 90), mSurface.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_navBarLeft() { + setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(10, 0, 100, 100), mSurface.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_navBarRight() { + setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 90, 100), mSurface.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotFrame() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 0, 10); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + assertEquals(new Rect(0, -10, 100, 70), + mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); + } + + @Test + public void testCalculateSnapshotFrame_navBarLeft() { + setupSurface(100, 100); + final Rect insets = new Rect(10, 10, 0, 0); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + assertEquals(new Rect(0, -10, 90, 80), + mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); + } + + @Test + public void testDrawStatusBarBackground() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 10, 0); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground( + mockCanvas, new Rect(0, 0, 50, 100), 10); + verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); + } + + @Test + public void testDrawStatusBarBackground_nullFrame() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 10, 0); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground( + mockCanvas, null, 10); + verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); + } + + @Test + public void testDrawStatusBarBackground_nope() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 10, 0); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.mSystemBarBackgroundPainter.drawStatusBarBackground( + mockCanvas, new Rect(0, 0, 100, 100), 10); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void testDrawNavigationBarBackground() { + final Rect insets = new Rect(0, 10, 0, 10); + setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, 100, 100)); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas); + verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); + } + + @Test + public void testDrawNavigationBarBackground_left() { + final Rect insets = new Rect(10, 10, 0, 0); + setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, 100, 100)); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas); + verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); + } + + @Test + public void testDrawNavigationBarBackground_right() { + final Rect insets = new Rect(0, 10, 10, 0); + setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, 100, 100)); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mSurface.mSystemBarBackgroundPainter.drawNavigationBarBackground(mockCanvas); + verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java new file mode 100644 index 000000000000..e66b0d9c841d --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}. + * + * Build/Install/Run: + * atest WmTests:TaskStackContainersTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskStackContainersTests extends WindowTestsBase { + + private TaskStack mPinnedStack; + + @Before + public void setUp() throws Exception { + super.setUp(); + mPinnedStack = createStackControllerOnStackOnDisplay( + WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; + // Stack should contain visible app window to be considered visible. + final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */); + assertFalse(mPinnedStack.isVisible()); + final WindowTestUtils.TestAppWindowToken pinnedApp = + WindowTestUtils.createTestAppWindowToken(mDisplayContent); + pinnedTask.addChild(pinnedApp, 0 /* addPos */); + assertTrue(mPinnedStack.isVisible()); + } + + @After + public void tearDown() throws Exception { + mPinnedStack.removeImmediately(); + } + + @Test + public void testStackPositionChildAt() throws Exception { + // Test that always-on-top stack can't be moved to position other than top. + final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent); + final TaskStack stack2 = createTaskStackOnDisplay(mDisplayContent); + + final WindowContainer taskStackContainer = stack1.getParent(); + + final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1); + final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2); + final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack); + assertGreaterThan(pinnedStackPos, stack2Pos); + assertGreaterThan(stack2Pos, stack1Pos); + + taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false); + assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); + assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); + + taskStackContainer.positionChildAt(1, mPinnedStack, false); + assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1); + assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); + } + + @Test + public void testStackPositionBelowPinnedStack() throws Exception { + // Test that no stack can be above pinned stack. + final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent); + + final WindowContainer taskStackContainer = stack1.getParent(); + + final int stackPos = taskStackContainer.mChildren.indexOf(stack1); + final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack); + assertGreaterThan(pinnedStackPos, stackPos); + + taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false); + assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); + + taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false); + assertEquals(taskStackContainer.mChildren.get(stackPos), stack1); + assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java new file mode 100644 index 000000000000..0deb4af542c4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link TaskStack} class. + * + * Build/Install/Run: + * atest WmTests:TaskStackTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskStackTests extends WindowTestsBase { + + @Test + public void testStackPositionChildAt() throws Exception { + final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 1 /* userId */); + + // Current user task should be moved to top. + stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */); + assertEquals(stack.mChildren.get(0), task2); + assertEquals(stack.mChildren.get(1), task1); + + // Non-current user won't be moved to top. + stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */); + assertEquals(stack.mChildren.get(0), task2); + assertEquals(stack.mChildren.get(1), task1); + } + + @Test + public void testClosingAppDifferentStackOrientation() throws Exception { + final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack, 0 /* userId */); + WindowTestUtils.TestAppWindowToken appWindowToken1 = + WindowTestUtils.createTestAppWindowToken(mDisplayContent); + task1.addChild(appWindowToken1, 0); + appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + final Task task2 = createTaskInStack(stack, 1 /* userId */); + WindowTestUtils.TestAppWindowToken appWindowToken2 = + WindowTestUtils.createTestAppWindowToken(mDisplayContent); + task2.addChild(appWindowToken2, 0); + appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); + + assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation()); + sWm.mClosingApps.add(appWindowToken2); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation()); + } + + @Test + public void testMoveTaskToBackDifferentStackOrientation() throws Exception { + final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task1 = createTaskInStack(stack, 0 /* userId */); + WindowTestUtils.TestAppWindowToken appWindowToken1 = + WindowTestUtils.createTestAppWindowToken(mDisplayContent); + task1.addChild(appWindowToken1, 0); + appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + + final Task task2 = createTaskInStack(stack, 1 /* userId */); + WindowTestUtils.TestAppWindowToken appWindowToken2 = + WindowTestUtils.createTestAppWindowToken(mDisplayContent); + task2.addChild(appWindowToken2, 0); + appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT); + + assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation()); + task2.setSendingToBottom(true); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation()); + } + + @Test + public void testStackRemoveImmediately() throws Exception { + final TaskStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + assertEquals(stack, task.mStack); + + // Remove stack and check if its child is also removed. + stack.removeImmediately(); + assertNull(stack.getDisplayContent()); + assertNull(task.mStack); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java new file mode 100644 index 000000000000..e734bc0615c6 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskWindowContainerControllerTests.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskWindowContainerController}. + * + * Build/Install/Run: + * atest WmTests:TaskWindowContainerControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TaskWindowContainerControllerTests extends WindowTestsBase { + + @Test + public void testRemoveContainer() throws Exception { + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(this); + final WindowTestUtils.TestAppWindowContainerController appController = + new WindowTestUtils.TestAppWindowContainerController(taskController); + + taskController.removeContainer(); + // Assert that the container was removed. + assertNull(taskController.mContainer); + assertNull(appController.mContainer); + } + + @Test + public void testRemoveContainer_deferRemoval() throws Exception { + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(this); + final WindowTestUtils.TestAppWindowContainerController appController = + new WindowTestUtils.TestAppWindowContainerController(taskController); + + final WindowTestUtils.TestTask task = (WindowTestUtils.TestTask) taskController.mContainer; + final AppWindowToken app = appController.mContainer; + task.mShouldDeferRemoval = true; + + taskController.removeContainer(); + // For the case of deferred removal the task controller will no longer be connected to the + // container, but the app controller will still be connected to the its container until + // the task window container is removed. + assertNull(taskController.mContainer); + assertNull(task.getController()); + assertNotNull(appController.mContainer); + assertNotNull(app.getController()); + + task.removeImmediately(); + assertNull(appController.mContainer); + assertNull(app.getController()); + } + + @Test + public void testReparent() throws Exception { + final StackWindowController stackController1 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(stackController1); + final StackWindowController stackController2 = + createStackControllerOnDisplay(mDisplayContent); + final WindowTestUtils.TestTaskWindowContainerController taskController2 = + new WindowTestUtils.TestTaskWindowContainerController(stackController2); + + boolean gotException = false; + try { + taskController.reparent(stackController1, 0, false/* moveParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue("Should not be able to reparent to the same parent", gotException); + + final StackWindowController stackController3 = + createStackControllerOnDisplay(mDisplayContent); + stackController3.setContainer(null); + gotException = false; + try { + taskController.reparent(stackController3, 0, false/* moveParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue("Should not be able to reparent to a stack that doesn't have a container", + gotException); + + taskController.reparent(stackController2, 0, false/* moveParents */); + assertEquals(stackController2.mContainer, taskController.mContainer.getParent()); + assertEquals(0, ((WindowTestUtils.TestTask) taskController.mContainer).positionInParent()); + assertEquals(1, ((WindowTestUtils.TestTask) taskController2.mContainer).positionInParent()); + } + + @Test + public void testReparent_BetweenDisplays() throws Exception { + // Create first stack on primary display. + final StackWindowController stack1Controller = + createStackControllerOnDisplay(mDisplayContent); + final TaskStack stack1 = stack1Controller.mContainer; + final WindowTestUtils.TestTaskWindowContainerController taskController = + new WindowTestUtils.TestTaskWindowContainerController(stack1Controller); + final WindowTestUtils.TestTask task1 = (WindowTestUtils.TestTask) taskController.mContainer; + task1.mOnDisplayChangedCalled = false; + assertEquals(mDisplayContent, stack1.getDisplayContent()); + + // Create second display and put second stack on it. + final DisplayContent dc = createNewDisplay(); + final StackWindowController stack2Controller = createStackControllerOnDisplay(dc); + final TaskStack stack2 = stack2Controller.mContainer; + final WindowTestUtils.TestTaskWindowContainerController taskController2 = + new WindowTestUtils.TestTaskWindowContainerController(stack2Controller); + final WindowTestUtils.TestTask task2 = + (WindowTestUtils.TestTask) taskController2.mContainer; + + // Reparent and check state + taskController.reparent(stack2Controller, 0, false /* moveParents */); + assertEquals(stack2, task1.getParent()); + assertEquals(0, task1.positionInParent()); + assertEquals(1, task2.positionInParent()); + assertTrue(task1.mOnDisplayChangedCalled); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java new file mode 100644 index 000000000000..032f4162b48e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import android.graphics.Rect; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.MergedConfiguration; +import android.view.DisplayCutout; +import android.view.DragEvent; +import android.view.IWindow; + +import com.android.internal.os.IResultReceiver; + +public class TestIWindow extends IWindow.Stub { + @Override + public void executeCommand(String command, String parameters, + ParcelFileDescriptor descriptor) + throws RemoteException { + + } + + @Override + public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, + Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfig, + Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId, + DisplayCutout.ParcelableWrapper displayCutout) + throws RemoteException { + + } + + @Override + public void moved(int newX, int newY) throws RemoteException { + + } + + @Override + public void dispatchAppVisibility(boolean visible) throws RemoteException { + + } + + @Override + public void dispatchGetNewSurface() throws RemoteException { + + } + + @Override + public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) + throws RemoteException { + + } + + @Override + public void closeSystemDialogs(String reason) throws RemoteException { + + } + + @Override + public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, + boolean sync) + throws RemoteException { + + } + + @Override + public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, + boolean sync) throws RemoteException { + + } + + @Override + public void dispatchDragEvent(DragEvent event) throws RemoteException { + + } + + @Override + public void updatePointerIcon(float x, float y) throws RemoteException { + + } + + @Override + public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, int localValue, + int localChanges) throws RemoteException { + + } + + @Override + public void dispatchWindowShown() throws RemoteException { + + } + + @Override + public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) + throws RemoteException { + + } + + @Override + public void dispatchPointerCaptureChanged(boolean hasCapture) { + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java new file mode 100644 index 000000000000..ee028ba4262b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; + +import static org.mockito.Mockito.mock; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.proto.ProtoOutputStream; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.IWindow; +import android.view.IWindowManager; +import android.view.KeyEvent; +import android.view.WindowManager; +import android.view.animation.Animation; + +import com.android.internal.policy.IKeyguardDismissCallback; +import com.android.internal.policy.IShortcutService; +import com.android.server.policy.WindowManagerPolicy; + +import java.io.PrintWriter; +import java.util.function.Supplier; + +class TestWindowManagerPolicy implements WindowManagerPolicy { + private static final String TAG = "TestWindowManagerPolicy"; + + private final Supplier mWmSupplier; + + int rotationToReport = 0; + boolean keyguardShowingAndNotOccluded = false; + + private Runnable mRunnableWhenAddingSplashScreen; + + public TestWindowManagerPolicy(Supplier wmSupplier) { + + mWmSupplier = wmSupplier; + } + + @Override + public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver) + throws RemoteException { + + } + + @Override + public void init(Context context, IWindowManager windowManager, + WindowManagerFuncs windowManagerFuncs) { + + } + + @Override + public boolean isDefaultOrientationForced() { + return false; + } + + @Override + public void setInitialDisplaySize(Display display, int width, int height, int density) { + + } + + @Override + public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) { + return 0; + } + + @Override + public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs) { + return false; + } + + @Override + public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs, + boolean hasStatusBarServicePermission) { + } + + @Override + public void adjustConfigurationLw(Configuration config, int keyboardPresence, + int navigationPresence) { + + } + + @Override + public int getMaxWallpaperLayer() { + return 0; + } + + @Override + public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, + int displayId, DisplayCutout displayCutout) { + return 0; + } + + @Override + public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, + int displayId, DisplayCutout displayCutout) { + return 0; + } + + @Override + public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode, + int displayId, DisplayCutout displayCutout) { + return 0; + } + + @Override + public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode, + int displayId, DisplayCutout displayCutout) { + return 0; + } + + @Override + public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) { + return attrs.type == TYPE_STATUS_BAR; + } + + @Override + public boolean canBeHiddenByKeyguardLw(WindowState win) { + return false; + } + + /** + * Sets a runnable to run when adding a splash screen which gets executed after the window has + * been added but before returning the surface. + */ + void setRunnableWhenAddingSplashScreen(Runnable r) { + mRunnableWhenAddingSplashScreen = r; + } + + @Override + public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme, + CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, + int logo, int windowFlags, Configuration overrideConfig, int displayId) { + final com.android.server.wm.WindowState window; + final AppWindowToken atoken; + final WindowManagerService wm = mWmSupplier.get(); + synchronized (wm.mWindowMap) { + atoken = wm.mRoot.getAppWindowToken(appToken); + window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken, + "Starting window", 0 /* ownerId */, false /* internalWindows */, wm, + mock(Session.class), mock(IWindow.class)); + atoken.startingWindow = window; + } + if (mRunnableWhenAddingSplashScreen != null) { + mRunnableWhenAddingSplashScreen.run(); + mRunnableWhenAddingSplashScreen = null; + } + return () -> { + synchronized (wm.mWindowMap) { + atoken.removeChild(window); + atoken.startingWindow = null; + } + }; + } + + @Override + public int prepareAddWindowLw(WindowState win, + WindowManager.LayoutParams attrs) { + return 0; + } + + @Override + public void removeWindowLw(WindowState win) { + + } + + @Override + public int selectAnimationLw(WindowState win, int transit) { + return 0; + } + + @Override + public void selectRotationAnimationLw(int[] anim) { + + } + + @Override + public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId, + boolean forceDefault) { + return false; + } + + @Override + public Animation createHiddenByKeyguardExit(boolean onWallpaper, + boolean goingToNotificationShade) { + return null; + } + + @Override + public Animation createKeyguardWallpaperExit(boolean goingToNotificationShade) { + return null; + } + + @Override + public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { + return 0; + } + + @Override + public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + return 0; + } + + @Override + public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, + int policyFlags) { + return 0; + } + + @Override + public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, + int policyFlags) { + return null; + } + + @Override + public int getSystemDecorLayerLw() { + return 0; + } + + @Override + public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) { + + } + + @Override + public void applyPostLayoutPolicyLw(WindowState win, + WindowManager.LayoutParams attrs, WindowState attached, WindowState imeTarget) { + } + + @Override + public int finishPostLayoutPolicyLw() { + return 0; + } + + @Override + public boolean allowAppAnimationsLw() { + return false; + } + + @Override + public int focusChangedLw(WindowState lastFocus, + WindowState newFocus) { + return 0; + } + + @Override + public void startedWakingUp() { + + } + + @Override + public void finishedWakingUp() { + + } + + @Override + public void startedGoingToSleep(int why) { + + } + + @Override + public void finishedGoingToSleep(int why) { + + } + + @Override + public void screenTurningOn(ScreenOnListener screenOnListener) { + + } + + @Override + public void screenTurnedOn() { + + } + + @Override + public void screenTurningOff(ScreenOffListener screenOffListener) { + + } + + @Override + public void screenTurnedOff() { + + } + + @Override + public boolean isScreenOn() { + return true; + } + + @Override + public boolean okToAnimate() { + return true; + } + + @Override + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + + } + + @Override + public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) { + + } + + @Override + public void enableKeyguard(boolean enabled) { + + } + + @Override + public void exitKeyguardSecurely(OnKeyguardExitResult callback) { + + } + + @Override + public boolean isKeyguardLocked() { + return keyguardShowingAndNotOccluded; + } + + @Override + public boolean isKeyguardSecure(int userId) { + return false; + } + + @Override + public boolean isKeyguardOccluded() { + return false; + } + + @Override + public boolean isKeyguardTrustedLw() { + return false; + } + + @Override + public boolean isKeyguardShowingAndNotOccluded() { + return keyguardShowingAndNotOccluded; + } + + @Override + public boolean inKeyguardRestrictedKeyInputMode() { + return false; + } + + @Override + public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback, + CharSequence message) { + } + + @Override + public boolean isKeyguardDrawnLw() { + return false; + } + + @Override + public boolean isShowingDreamLw() { + return false; + } + + @Override + public void onKeyguardOccludedChangedLw(boolean occluded) { + } + + @Override + public int rotationForOrientationLw(int orientation, int lastRotation, boolean defaultDisplay) { + return rotationToReport; + } + + @Override + public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation) { + return true; + } + + @Override + public void setRotationLw(int rotation) { + + } + + @Override + public void setSafeMode(boolean safeMode) { + + } + + @Override + public void systemReady() { + + } + + @Override + public void systemBooted() { + + } + + @Override + public void showBootMessage(CharSequence msg, boolean always) { + + } + + @Override + public void hideBootMessages() { + + } + + @Override + public void userActivity() { + + } + + @Override + public void enableScreenAfterBoot() { + + } + + @Override + public void setCurrentOrientationLw(int newOrientation) { + + } + + @Override + public boolean performHapticFeedbackLw(WindowState win, int effectId, + boolean always) { + return false; + } + + @Override + public void keepScreenOnStartedLw() { + + } + + @Override + public void keepScreenOnStoppedLw() { + + } + + @Override + public int getUserRotationMode() { + return 0; + } + + @Override + public void setUserRotationMode(int mode, + int rotation) { + + } + + @Override + public int adjustSystemUiVisibilityLw(int visibility) { + return 0; + } + + @Override + public boolean hasNavigationBar() { + return false; + } + + @Override + public void lockNow(Bundle options) { + + } + + @Override + public void setLastInputMethodWindowLw(WindowState ime, + WindowState target) { + + } + + @Override + public void showRecentApps() { + + } + + @Override + public void showGlobalActions() { + + } + + @Override + public void setCurrentUserLw(int newUserId) { + + } + + @Override + public void setSwitchingUser(boolean switching) { + + } + + @Override + public void writeToProto(ProtoOutputStream proto, long fieldId) { + + } + + @Override + public void dump(String prefix, PrintWriter writer, String[] args) { + + } + + @Override + public boolean isTopLevelWindow(int windowType) { + return false; + } + + @Override + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + + } + + @Override + public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight, + DisplayCutout cutout, Rect outInsets) { + + } + + @Override + public boolean isNavBarForcedShownLw(WindowState win) { + return false; + } + + @NavigationBarPosition + @Override + public int getNavBarPosition() { + return NAV_BAR_BOTTOM; + } + + @Override + public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight, + DisplayCutout cutout, Rect outInsets) { + + } + + @Override + public boolean isDockSideAllowed(int dockSide, int originalDockSide, int displayWidth, + int displayHeight, int displayRotation) { + return false; + } + + @Override + public void onConfigurationChanged() { + + } + + @Override + public boolean shouldRotateSeamlessly(int oldRotation, int newRotation) { + return false; + } + + @Override + public void setPipVisibilityLw(boolean visible) { + + } + + @Override + public void setRecentsVisibilityLw(boolean visible) { + + } + + @Override + public void setNavBarVirtualKeyHapticFeedbackEnabledLw(boolean enabled) { + } + + @Override + public void onSystemUiStarted() { + } + + @Override + public boolean canDismissBootAnimation() { + return true; + } + + @Override + public void requestUserActivityNotification() { + } + + @Override + public void onLockTaskStateChangedLw(int lockTaskState) { + } + + @Override + public boolean setAodShowing(boolean aodShowing) { + return false; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java new file mode 100644 index 000000000000..903352b7bec5 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static junit.framework.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link AppTransition}. + * + * Build/Install/Run: + * atest WmTests:UnknownAppVisibilityControllerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class UnknownAppVisibilityControllerTest extends WindowTestsBase { + + @Before + public void setUp() throws Exception { + super.setUp(); + sWm.mUnknownAppVisibilityController.clear(); + } + + @Test + public void testFlow() throws Exception { + final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + sWm.mUnknownAppVisibilityController.notifyLaunched(token); + sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token); + sWm.mUnknownAppVisibilityController.notifyRelayouted(token); + + // Make sure our handler processed the message. + Thread.sleep(100); + assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); + } + + @Test + public void testMultiple() throws Exception { + final AppWindowToken token1 = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + final AppWindowToken token2 = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + sWm.mUnknownAppVisibilityController.notifyLaunched(token1); + sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token1); + sWm.mUnknownAppVisibilityController.notifyLaunched(token2); + sWm.mUnknownAppVisibilityController.notifyRelayouted(token1); + sWm.mUnknownAppVisibilityController.notifyAppResumedFinished(token2); + sWm.mUnknownAppVisibilityController.notifyRelayouted(token2); + + // Make sure our handler processed the message. + Thread.sleep(100); + assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); + } + + @Test + public void testClear() throws Exception { + final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + sWm.mUnknownAppVisibilityController.notifyLaunched(token); + sWm.mUnknownAppVisibilityController.clear();; + assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); + } + + @Test + public void testAppRemoved() throws Exception { + final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + sWm.mUnknownAppVisibilityController.notifyLaunched(token); + sWm.mUnknownAppVisibilityController.appRemovedOrHidden(token); + assertTrue(sWm.mUnknownAppVisibilityController.allResolved()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java new file mode 100644 index 000000000000..71ead204c9df --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -0,0 +1,67 @@ +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; + +import static junit.framework.TestCase.assertNotNull; + +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.graphics.Bitmap; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link WallpaperController} class. + * + * Build/Install/Run: + * atest com.android.server.wm.WallpaperControllerTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WallpaperControllerTests extends WindowTestsBase { + @Test + public void testWallpaperScreenshot() { + WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class); + + synchronized (sWm.mWindowMap) { + // No wallpaper + final DisplayContent dc = createNewDisplay(); + Bitmap wallpaperBitmap = sWm.mRoot.mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + // No wallpaper WSA Surface + WindowToken wallpaperWindowToken = new WallpaperWindowToken(sWm, mock(IBinder.class), + true, dc, true /* ownerCanManageAppTokens */); + WindowState wallpaperWindow = createWindow(null /* parent */, TYPE_WALLPAPER, + wallpaperWindowToken, "wallpaperWindow"); + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + // Wallpaper with not visible WSA surface. + wallpaperWindow.mWinAnimator.mSurfaceController = windowSurfaceController; + wallpaperWindow.mWinAnimator.mLastAlpha = 1; + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + when(windowSurfaceController.getShown()).thenReturn(true); + + // Wallpaper with WSA alpha set to 0. + wallpaperWindow.mWinAnimator.mLastAlpha = 0; + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNull(wallpaperBitmap); + + // Wallpaper window with WSA Surface + wallpaperWindow.mWinAnimator.mLastAlpha = 1; + wallpaperBitmap = mWallpaperController.screenshotWallpaperLocked(); + assertNotNull(wallpaperBitmap); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java new file mode 100644 index 000000000000..aac905e8ffd4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.view.animation.ClipRectAnimation; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link WindowAnimationSpec} class. + * + * Build/Install/Run: + * atest WmTests:WindowAnimationSpecTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowAnimationSpecTest { + private final SurfaceControl mSurfaceControl = mock(SurfaceControl.class); + private final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class); + private final Animation mAnimation = mock(Animation.class); + private final Rect mStackBounds = new Rect(0, 0, 10, 10); + + @Test + public void testApply_clipNone() { + Rect windowCrop = new Rect(0, 0, 20, 20); + Animation a = createClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_NONE, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(windowCrop))); + } + + @Test + public void testApply_clipAfter() { + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_AFTER_ANIM, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); + verify(mTransaction).setFinalCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(mStackBounds))); + } + + @Test + public void testApply_clipAfterOffsetPosition() { + // Stack bounds is (0, 0, 10, 10) position is (20, 40) + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, + new Point(20, 40), mStackBounds, false /* canSkipFirstFrame */, + STACK_CLIP_AFTER_ANIM, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); + verify(mTransaction).setFinalCrop(eq(mSurfaceControl), + argThat(rect -> rect.left == 20 && rect.top == 40 && rect.right == 30 + && rect.bottom == 50)); + } + + @Test + public void testApply_clipBeforeNoAnimationBounds() { + // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0) + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(mStackBounds))); + } + + @Test + public void testApply_clipBeforeNoStackBounds() { + // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20) + Rect windowCrop = new Rect(0, 0, 20, 20); + Animation a = createClipRectAnimation(windowCrop, windowCrop); + a.initialize(0, 0, 0, 0); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + null, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); + } + + @Test + public void testApply_clipBeforeSmallerAnimationClip() { + // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5) + Rect windowCrop = new Rect(0, 0, 5, 5); + Animation a = createClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(windowCrop))); + } + + @Test + public void testApply_clipBeforeSmallerStackClip() { + // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20) + Rect windowCrop = new Rect(0, 0, 20, 20); + Animation a = createClipRectAnimation(windowCrop, windowCrop); + WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null, + mStackBounds, false /* canSkipFirstFrame */, STACK_CLIP_BEFORE_ANIM, + true /* isAppAnimation */); + windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); + verify(mTransaction).setWindowCrop(eq(mSurfaceControl), + argThat(rect -> rect.equals(mStackBounds))); + } + + private Animation createClipRectAnimation(Rect fromClip, Rect toClip) { + Animation a = new ClipRectAnimation(fromClip, toClip); + a.initialize(0, 0, 0, 0); + return a; + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java new file mode 100644 index 000000000000..9c59a1748dbf --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowConfigurationTests.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS; +import static android.app.WindowConfiguration.WINDOW_CONFIG_WINDOWING_MODE; +import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.app.WindowConfiguration; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.DisplayInfo; + +import org.junit.Test; + +/** + * Test class to for {@link android.app.WindowConfiguration}. + * + * Build/Install/Run: + * atest WmTests:WindowConfigurationTests + */ +@SmallTest +@FlakyTest(bugId = 74078662) +@Presubmit +@org.junit.runner.RunWith(AndroidJUnit4.class) +public class WindowConfigurationTests extends WindowTestsBase { + private Rect mParentBounds; + + @Override + public void setUp() throws Exception { + super.setUp(); + mParentBounds = new Rect(10 /*left*/, 30 /*top*/, 80 /*right*/, 60 /*bottom*/); + } + + /** Tests {@link android.app.WindowConfiguration#diff(WindowConfiguration, boolean)}. */ + @Test + public void testDiff() { + final Configuration config1 = new Configuration(); + final WindowConfiguration winConfig1 = config1.windowConfiguration; + final Configuration config2 = new Configuration(); + final WindowConfiguration winConfig2 = config2.windowConfiguration; + final Configuration config3 = new Configuration(); + final WindowConfiguration winConfig3 = config3.windowConfiguration; + + winConfig1.setAppBounds(0, 1, 1, 0); + winConfig2.setAppBounds(1, 2, 2, 1); + winConfig3.setAppBounds(winConfig1.getAppBounds()); + + + assertEquals(CONFIG_WINDOW_CONFIGURATION, config1.diff(config2)); + assertEquals(0, config1.diffPublicOnly(config2)); + assertEquals(WINDOW_CONFIG_APP_BOUNDS, + winConfig1.diff(winConfig2, false /* compareUndefined */)); + + winConfig2.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertEquals(WINDOW_CONFIG_APP_BOUNDS | WINDOW_CONFIG_WINDOWING_MODE, + winConfig1.diff(winConfig2, false /* compareUndefined */)); + + assertEquals(0, config1.diff(config3)); + assertEquals(0, config1.diffPublicOnly(config3)); + assertEquals(0, winConfig1.diff(winConfig3, false /* compareUndefined */)); + } + + /** Tests {@link android.app.WindowConfiguration#compareTo(WindowConfiguration)}. */ + @Test + public void testConfigurationCompareTo() throws Exception { + final Configuration blankConfig = new Configuration(); + final WindowConfiguration blankWinConfig = new WindowConfiguration(); + + final Configuration config1 = new Configuration(); + final WindowConfiguration winConfig1 = config1.windowConfiguration; + winConfig1.setAppBounds(1, 2, 3, 4); + + final Configuration config2 = new Configuration(config1); + final WindowConfiguration winConfig2 = config2.windowConfiguration; + + assertEquals(config1.compareTo(config2), 0); + assertEquals(winConfig1.compareTo(winConfig2), 0); + + // Different windowing mode + winConfig2.setWindowingMode(WINDOWING_MODE_FREEFORM); + assertNotEquals(config1.compareTo(config2), 0); + assertNotEquals(winConfig1.compareTo(winConfig2), 0); + winConfig2.setWindowingMode(winConfig1.getWindowingMode()); + + // Different bounds + winConfig2.setAppBounds(0, 2, 3, 4); + assertNotEquals(config1.compareTo(config2), 0); + assertNotEquals(winConfig1.compareTo(winConfig2), 0); + + // No bounds + assertEquals(config1.compareTo(blankConfig), -1); + assertEquals(winConfig1.compareTo(blankWinConfig), -1); + + assertEquals(blankConfig.compareTo(config1), 1); + assertEquals(blankWinConfig.compareTo(winConfig1), 1); + } + + @Test + public void testSetActivityType() throws Exception { + final WindowConfiguration config = new WindowConfiguration(); + config.setActivityType(ACTIVITY_TYPE_HOME); + assertEquals(ACTIVITY_TYPE_HOME, config.getActivityType()); + + // Allowed to change from app process. + config.setActivityType(ACTIVITY_TYPE_STANDARD); + assertEquals(ACTIVITY_TYPE_STANDARD, config.getActivityType()); + } + + /** Ensures the configuration app bounds at the root level match the app dimensions. */ + @Test + public void testAppBounds_RootConfigurationBounds() throws Exception { + final DisplayInfo info = mDisplayContent.getDisplayInfo(); + info.appWidth = 1024; + info.appHeight = 768; + + final Rect appBounds = sWm.computeNewConfiguration( + mDisplayContent.getDisplayId()).windowConfiguration.getAppBounds(); + // The bounds should always be positioned in the top left. + assertEquals(appBounds.left, 0); + assertEquals(appBounds.top, 0); + + // The bounds should equal the defined app width and height + assertEquals(appBounds.width(), info.appWidth); + assertEquals(appBounds.height(), info.appHeight); + } + + /** Ensures that bounds are clipped to their parent. */ + @Test + public void testAppBounds_BoundsClipping() throws Exception { + final Rect shiftedBounds = new Rect(mParentBounds); + shiftedBounds.offset(10, 10); + final Rect expectedBounds = new Rect(mParentBounds); + expectedBounds.intersect(shiftedBounds); + testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds, + expectedBounds); + } + + /** Ensures that empty bounds are not propagated to the configuration. */ + @Test + public void testAppBounds_EmptyBounds() throws Exception { + final Rect emptyBounds = new Rect(); + testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds, + null /*ExpectedBounds*/); + } + + /** Ensures that bounds on freeform stacks are not clipped. */ + @Test + public void testAppBounds_FreeFormBounds() throws Exception { + final Rect freeFormBounds = new Rect(mParentBounds); + freeFormBounds.offset(10, 10); + testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds, + freeFormBounds); + } + + /** Ensures that fully contained bounds are not clipped. */ + @Test + public void testAppBounds_ContainedBounds() throws Exception { + final Rect insetBounds = new Rect(mParentBounds); + insetBounds.inset(5, 5, 5, 5); + testStackBoundsConfiguration( + WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds); + } + + /** Ensures that full screen free form bounds are clipped */ + @Test + public void testAppBounds_FullScreenFreeFormBounds() throws Exception { + final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth, + mDisplayInfo.logicalHeight); + testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds, + mParentBounds); + } + + private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds, + Rect expectedConfigBounds) { + final StackWindowController stackController = createStackControllerOnStackOnDisplay( + windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent); + + final Configuration parentConfig = mDisplayContent.getConfiguration(); + parentConfig.windowConfiguration.setAppBounds(parentBounds); + + final Configuration config = new Configuration(); + final WindowConfiguration winConfig = config.windowConfiguration; + stackController.adjustConfigurationForBounds(bounds, null /*insetBounds*/, + new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/, + false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig, + windowingMode); + // Assert that both expected and actual are null or are equal to each other + + assertEquals(expectedConfigBounds, winConfig.getAppBounds()); + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java new file mode 100644 index 000000000000..05f7c347f0b8 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerControllerTests.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.content.res.Configuration.EMPTY; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.res.Configuration; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; + +/** + * Test class for {@link WindowContainerController}. + * + * Build/Install/Run: + * atest WmTests:WindowContainerControllerTests + */ +@SmallTest +@Presubmit +@FlakyTest(bugId = 74078662) +@org.junit.runner.RunWith(AndroidJUnit4.class) +public class WindowContainerControllerTests extends WindowTestsBase { + + @Test + public void testCreation() throws Exception { + final WindowContainerController controller = new WindowContainerController(null, sWm); + final WindowContainer container = new WindowContainer(sWm); + + container.setController(controller); + assertEquals(controller, container.getController()); + assertEquals(controller.mContainer, container); + } + + @Test + public void testSetContainer() throws Exception { + final WindowContainerController controller = new WindowContainerController(null, sWm); + final WindowContainer container = new WindowContainer(sWm); + + controller.setContainer(container); + assertEquals(controller.mContainer, container); + + // Assert we can't change the container to another one once set + boolean gotException = false; + try { + controller.setContainer(new WindowContainer(sWm)); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + + // Assert that we can set the container to null. + controller.setContainer(null); + assertNull(controller.mContainer); + } + + @Test + public void testRemoveContainer() throws Exception { + final WindowContainerController controller = new WindowContainerController(null, sWm); + final WindowContainer container = new WindowContainer(sWm); + + controller.setContainer(container); + assertEquals(controller.mContainer, container); + + controller.removeContainer(); + assertNull(controller.mContainer); + } + + @Test + public void testOnOverrideConfigurationChanged() throws Exception { + final WindowContainerController controller = new WindowContainerController(null, sWm); + final WindowContainer container = new WindowContainer(sWm); + + controller.setContainer(container); + assertEquals(controller.mContainer, container); + assertEquals(EMPTY, container.getOverrideConfiguration()); + + final Configuration config = new Configuration(); + config.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + config.windowConfiguration.setAppBounds(10, 10, 10, 10); + + // Assert that the config change through the controller is propagated to the container. + controller.onOverrideConfigurationChanged(config); + assertEquals(config, container.getOverrideConfiguration()); + + // Assert the container configuration isn't changed after removal from the controller. + controller.removeContainer(); + controller.onOverrideConfigurationChanged(EMPTY); + assertEquals(config, container.getOverrideConfiguration()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java new file mode 100644 index 000000000000..5beeee151497 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -0,0 +1,871 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + +import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; +import static com.android.server.wm.WindowContainer.POSITION_TOP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyFloat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Comparator; + +/** + * Test class for {@link WindowContainer}. + * + * Build/Install/Run: + * atest WmTests:WindowContainerTests + */ +@SmallTest +@Presubmit +@FlakyTest(bugId = 74078662) +@RunWith(AndroidJUnit4.class) +public class WindowContainerTests extends WindowTestsBase { + + @Test + public void testCreation() throws Exception { + final TestWindowContainer w = new TestWindowContainerBuilder().setLayer(0).build(); + assertNull("window must have no parent", w.getParentWindow()); + assertEquals("window must have no children", 0, w.getChildrenCount()); + } + + @Test + public void testAdd() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer layer1 = root.addChildWindow(builder.setLayer(1)); + final TestWindowContainer secondLayer1 = root.addChildWindow(builder.setLayer(1)); + final TestWindowContainer layer2 = root.addChildWindow(builder.setLayer(2)); + final TestWindowContainer layerNeg1 = root.addChildWindow(builder.setLayer(-1)); + final TestWindowContainer layerNeg2 = root.addChildWindow(builder.setLayer(-2)); + final TestWindowContainer secondLayerNeg1 = root.addChildWindow(builder.setLayer(-1)); + final TestWindowContainer layer0 = root.addChildWindow(builder.setLayer(0)); + + assertEquals(7, root.getChildrenCount()); + + assertEquals(root, layer1.getParentWindow()); + assertEquals(root, secondLayer1.getParentWindow()); + assertEquals(root, layer2.getParentWindow()); + assertEquals(root, layerNeg1.getParentWindow()); + assertEquals(root, layerNeg2.getParentWindow()); + assertEquals(root, secondLayerNeg1.getParentWindow()); + assertEquals(root, layer0.getParentWindow()); + + assertEquals(layerNeg2, root.getChildAt(0)); + assertEquals(secondLayerNeg1, root.getChildAt(1)); + assertEquals(layerNeg1, root.getChildAt(2)); + assertEquals(layer0, root.getChildAt(3)); + assertEquals(layer1, root.getChildAt(4)); + assertEquals(secondLayer1, root.getChildAt(5)); + assertEquals(layer2, root.getChildAt(6)); + + assertTrue(layer1.mOnParentSetCalled); + assertTrue(secondLayer1.mOnParentSetCalled); + assertTrue(layer2.mOnParentSetCalled); + assertTrue(layerNeg1.mOnParentSetCalled); + assertTrue(layerNeg2.mOnParentSetCalled); + assertTrue(secondLayerNeg1.mOnParentSetCalled); + assertTrue(layer0.mOnParentSetCalled); + } + + @Test + public void testAddChildSetsSurfacePosition() throws Exception { + MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(); + + final SurfaceControl.Transaction transaction = mock(SurfaceControl.Transaction.class); + sWm.mTransactionFactory = () -> transaction; + + WindowContainer child = new WindowContainer(sWm); + child.setBounds(1, 1, 10, 10); + + verify(transaction, never()).setPosition(any(), anyFloat(), anyFloat()); + top.addChild(child, 0); + verify(transaction, times(1)).setPosition(any(), eq(1.f), eq(1.f)); + } + + @Test + public void testAdd_AlreadyHasParent() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + + boolean gotException = false; + try { + child1.addChildWindow(child2); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + + gotException = false; + try { + root.addChildWindow(child2); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + } + + @Test + public void testHasChild() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertEquals(2, root.getChildrenCount()); + assertEquals(2, child1.getChildrenCount()); + assertEquals(1, child2.getChildrenCount()); + + assertTrue(root.hasChild(child1)); + assertTrue(root.hasChild(child2)); + assertTrue(root.hasChild(child11)); + assertTrue(root.hasChild(child12)); + assertTrue(root.hasChild(child21)); + + assertTrue(child1.hasChild(child11)); + assertTrue(child1.hasChild(child12)); + assertFalse(child1.hasChild(child21)); + + assertTrue(child2.hasChild(child21)); + assertFalse(child2.hasChild(child11)); + assertFalse(child2.hasChild(child12)); + } + + @Test + public void testRemoveImmediately() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertNotNull(child12.getParentWindow()); + child12.removeImmediately(); + assertNull(child12.getParentWindow()); + assertEquals(1, child1.getChildrenCount()); + assertFalse(child1.hasChild(child12)); + assertFalse(root.hasChild(child12)); + + assertTrue(root.hasChild(child2)); + assertNotNull(child2.getParentWindow()); + child2.removeImmediately(); + assertNull(child2.getParentWindow()); + assertNull(child21.getParentWindow()); + assertEquals(0, child2.getChildrenCount()); + assertEquals(1, root.getChildrenCount()); + assertFalse(root.hasChild(child2)); + assertFalse(root.hasChild(child21)); + + assertTrue(root.hasChild(child1)); + assertTrue(root.hasChild(child11)); + + root.removeImmediately(); + assertEquals(0, root.getChildrenCount()); + } + + @Test + public void testRemoveImmediately_WithController() throws Exception { + final WindowContainer container = new WindowContainer(sWm); + final WindowContainerController controller = new WindowContainerController(null, sWm); + + container.setController(controller); + assertEquals(controller, container.getController()); + assertEquals(container, controller.mContainer); + + container.removeImmediately(); + assertNull(container.getController()); + assertNull(controller.mContainer); + } + + @Test + public void testSetController() throws Exception { + final WindowContainerController controller = new WindowContainerController(null, sWm); + final WindowContainer container = new WindowContainer(sWm); + + container.setController(controller); + assertEquals(controller, container.getController()); + assertEquals(container, controller.mContainer); + + // Assert we can't change the controller to another one once set + boolean gotException = false; + try { + container.setController(new WindowContainerController(null, sWm)); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + + // Assert that we can set the controller to null. + container.setController(null); + assertNull(container.getController()); + assertNull(controller.mContainer); + } + + @Test + public void testPositionChildAt() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child3 = root.addChildWindow(); + + // Test position at top. + root.positionChildAt(POSITION_TOP, child1, false /* includingParents */); + assertEquals(child1, root.getChildAt(root.getChildrenCount() - 1)); + + // Test position at bottom. + root.positionChildAt(POSITION_BOTTOM, child1, false /* includingParents */); + assertEquals(child1, root.getChildAt(0)); + + // Test position in the middle. + root.positionChildAt(1, child3, false /* includingParents */); + assertEquals(child1, root.getChildAt(0)); + assertEquals(child3, root.getChildAt(1)); + assertEquals(child2, root.getChildAt(2)); + } + + @Test + public void testPositionChildAtIncludeParents() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + final TestWindowContainer child13 = child1.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + final TestWindowContainer child22 = child2.addChildWindow(); + final TestWindowContainer child23 = child2.addChildWindow(); + + // Test moving to top. + child1.positionChildAt(POSITION_TOP, child11, true /* includingParents */); + assertEquals(child12, child1.getChildAt(0)); + assertEquals(child13, child1.getChildAt(1)); + assertEquals(child11, child1.getChildAt(2)); + assertEquals(child2, root.getChildAt(0)); + assertEquals(child1, root.getChildAt(1)); + + // Test moving to bottom. + child1.positionChildAt(POSITION_BOTTOM, child11, true /* includingParents */); + assertEquals(child11, child1.getChildAt(0)); + assertEquals(child12, child1.getChildAt(1)); + assertEquals(child13, child1.getChildAt(2)); + assertEquals(child1, root.getChildAt(0)); + assertEquals(child2, root.getChildAt(1)); + + // Test moving to middle, includeParents shouldn't do anything. + child2.positionChildAt(1, child21, true /* includingParents */); + assertEquals(child11, child1.getChildAt(0)); + assertEquals(child12, child1.getChildAt(1)); + assertEquals(child13, child1.getChildAt(2)); + assertEquals(child22, child2.getChildAt(0)); + assertEquals(child21, child2.getChildAt(1)); + assertEquals(child23, child2.getChildAt(2)); + assertEquals(child1, root.getChildAt(0)); + assertEquals(child2, root.getChildAt(1)); + } + + @Test + public void testPositionChildAtInvalid() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + + boolean gotException = false; + try { + // Check response to negative position. + root.positionChildAt(-1, child1, false /* includingParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + + gotException = false; + try { + // Check response to position that's bigger than child number. + root.positionChildAt(3, child1, false /* includingParents */); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + } + + @Test + public void testIsAnimating() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(builder.setIsAnimating(true)); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(builder.setIsAnimating(true)); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertFalse(root.isAnimating()); + assertTrue(child1.isAnimating()); + assertTrue(child11.isAnimating()); + assertTrue(child12.isAnimating()); + assertFalse(child2.isAnimating()); + assertFalse(child21.isAnimating()); + + assertTrue(root.isSelfOrChildAnimating()); + assertTrue(child1.isSelfOrChildAnimating()); + assertFalse(child11.isSelfOrChildAnimating()); + assertTrue(child12.isSelfOrChildAnimating()); + assertFalse(child2.isSelfOrChildAnimating()); + assertFalse(child21.isSelfOrChildAnimating()); + } + + @Test + public void testIsVisible() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(builder.setIsVisible(true)); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(builder.setIsVisible(true)); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertFalse(root.isVisible()); + assertTrue(child1.isVisible()); + assertFalse(child11.isVisible()); + assertTrue(child12.isVisible()); + assertFalse(child2.isVisible()); + assertFalse(child21.isVisible()); + } + + @Test + public void testOverrideConfigurationAncestorNotification() { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer grandparent = builder.setLayer(0).build(); + + final TestWindowContainer parent = grandparent.addChildWindow(); + final TestWindowContainer child = parent.addChildWindow(); + child.onOverrideConfigurationChanged(new Configuration()); + + assertTrue(grandparent.mOnDescendantOverrideCalled); + } + + @Test + public void testRemoveChild() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + + assertTrue(root.hasChild(child2)); + assertTrue(root.hasChild(child21)); + root.removeChild(child2); + assertFalse(root.hasChild(child2)); + assertFalse(root.hasChild(child21)); + assertNull(child2.getParentWindow()); + + boolean gotException = false; + assertTrue(root.hasChild(child11)); + try { + // Can only detach our direct children. + root.removeChild(child11); + } catch (IllegalArgumentException e) { + gotException = true; + } + assertTrue(gotException); + } + + @Test + public void testGetOrientation_childSpecified() throws Exception { + testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_LANDSCAPE, + SCREEN_ORIENTATION_LANDSCAPE); + testGetOrientation_childSpecifiedConfig(false, SCREEN_ORIENTATION_UNSET, + SCREEN_ORIENTATION_UNSPECIFIED); + } + + private void testGetOrientation_childSpecifiedConfig(boolean childVisible, int childOrientation, + int expectedOrientation) { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + root.setFillsParent(true); + + builder.setIsVisible(childVisible); + + if (childOrientation != SCREEN_ORIENTATION_UNSET) { + builder.setOrientation(childOrientation); + } + + final TestWindowContainer child1 = root.addChildWindow(builder); + child1.setFillsParent(true); + + assertEquals(expectedOrientation, root.getOrientation()); + } + + @Test + public void testGetOrientation_Unset() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); + // Unspecified well because we didn't specify anything... + assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, root.getOrientation()); + } + + @Test + public void testGetOrientation_InvisibleParentUnsetVisibleChildren() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); + + builder.setIsVisible(false).setLayer(-1); + final TestWindowContainer invisible = root.addChildWindow(builder); + builder.setIsVisible(true).setLayer(-2); + final TestWindowContainer invisibleChild1VisibleAndSet = invisible.addChildWindow(builder); + invisibleChild1VisibleAndSet.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); + // Landscape well because the container is visible and that is what we set on it above. + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisibleChild1VisibleAndSet.getOrientation()); + // Landscape because even though the container isn't visible it has a child that is + // specifying it can influence the orientation by being visible. + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, invisible.getOrientation()); + // Landscape because the grandchild is visible and therefore can participate. + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation()); + + builder.setIsVisible(true).setLayer(-3); + final TestWindowContainer visibleUnset = root.addChildWindow(builder); + visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET); + assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnset.getOrientation()); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, root.getOrientation()); + + } + + @Test + public void testGetOrientation_setBehind() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); + + builder.setIsVisible(true).setLayer(-1); + final TestWindowContainer visibleUnset = root.addChildWindow(builder); + visibleUnset.setOrientation(SCREEN_ORIENTATION_UNSET); + + builder.setIsVisible(true).setLayer(-2); + final TestWindowContainer visibleUnsetChild1VisibleSetBehind = + visibleUnset.addChildWindow(builder); + visibleUnsetChild1VisibleSetBehind.setOrientation(SCREEN_ORIENTATION_BEHIND); + // Setting to visible behind will be used by the parents if there isn't another other + // container behind this one that has an orientation set. + assertEquals(SCREEN_ORIENTATION_BEHIND, + visibleUnsetChild1VisibleSetBehind.getOrientation()); + assertEquals(SCREEN_ORIENTATION_BEHIND, visibleUnset.getOrientation()); + assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation()); + } + + @Test + public void testGetOrientation_fillsParent() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).setIsVisible(true).build(); + + builder.setIsVisible(true).setLayer(-1); + final TestWindowContainer visibleUnset = root.addChildWindow(builder); + visibleUnset.setOrientation(SCREEN_ORIENTATION_BEHIND); + + builder.setLayer(1).setIsVisible(true); + final TestWindowContainer visibleUnspecifiedRootChild = root.addChildWindow(builder); + visibleUnspecifiedRootChild.setFillsParent(false); + visibleUnspecifiedRootChild.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + // Unset because the child doesn't fill the parent. May as well be invisible... + assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation()); + // The parent uses whatever orientation is set behind this container since it doesn't fill + // the parent. + assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation()); + + // Test case of child filling its parent, but its parent isn't filling its own parent. + builder.setLayer(2).setIsVisible(true); + final TestWindowContainer visibleUnspecifiedRootChildChildFillsParent = + visibleUnspecifiedRootChild.addChildWindow(builder); + visibleUnspecifiedRootChildChildFillsParent.setOrientation( + SCREEN_ORIENTATION_PORTRAIT); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, + visibleUnspecifiedRootChildChildFillsParent.getOrientation()); + assertEquals(SCREEN_ORIENTATION_UNSET, visibleUnspecifiedRootChild.getOrientation()); + assertEquals(SCREEN_ORIENTATION_BEHIND, root.getOrientation()); + + + visibleUnspecifiedRootChild.setFillsParent(true); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, visibleUnspecifiedRootChild.getOrientation()); + assertEquals(SCREEN_ORIENTATION_PORTRAIT, root.getOrientation()); + } + + @Test + public void testCompareTo() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.setLayer(0).build(); + + final TestWindowContainer child1 = root.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + + final TestWindowContainer child2 = root.addChildWindow(); + final TestWindowContainer child21 = child2.addChildWindow(); + final TestWindowContainer child22 = child2.addChildWindow(); + final TestWindowContainer child23 = child2.addChildWindow(); + final TestWindowContainer child221 = child22.addChildWindow(); + final TestWindowContainer child222 = child22.addChildWindow(); + final TestWindowContainer child223 = child22.addChildWindow(); + final TestWindowContainer child2221 = child222.addChildWindow(); + final TestWindowContainer child2222 = child222.addChildWindow(); + final TestWindowContainer child2223 = child222.addChildWindow(); + + final TestWindowContainer root2 = builder.setLayer(0).build(); + + assertEquals(0, root.compareTo(root)); + assertEquals(-1, child1.compareTo(child2)); + assertEquals(1, child2.compareTo(child1)); + + boolean inTheSameTree = true; + try { + root.compareTo(root2); + } catch (IllegalArgumentException e) { + inTheSameTree = false; + } + assertFalse(inTheSameTree); + + assertEquals(-1, child1.compareTo(child11)); + assertEquals(1, child21.compareTo(root)); + assertEquals(1, child21.compareTo(child12)); + assertEquals(-1, child11.compareTo(child2)); + assertEquals(1, child2221.compareTo(child11)); + assertEquals(-1, child2222.compareTo(child223)); + assertEquals(1, child2223.compareTo(child21)); + } + + @Test + public void testPrefixOrderIndex() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + + final TestWindowContainer child1 = root.addChildWindow(); + + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + + final TestWindowContainer child2 = root.addChildWindow(); + + final TestWindowContainer child21 = child2.addChildWindow(); + final TestWindowContainer child22 = child2.addChildWindow(); + + final TestWindowContainer child221 = child22.addChildWindow(); + final TestWindowContainer child222 = child22.addChildWindow(); + final TestWindowContainer child223 = child22.addChildWindow(); + + final TestWindowContainer child23 = child2.addChildWindow(); + + assertEquals(0, root.getPrefixOrderIndex()); + assertEquals(1, child1.getPrefixOrderIndex()); + assertEquals(2, child11.getPrefixOrderIndex()); + assertEquals(3, child12.getPrefixOrderIndex()); + assertEquals(4, child2.getPrefixOrderIndex()); + assertEquals(5, child21.getPrefixOrderIndex()); + assertEquals(6, child22.getPrefixOrderIndex()); + assertEquals(7, child221.getPrefixOrderIndex()); + assertEquals(8, child222.getPrefixOrderIndex()); + assertEquals(9, child223.getPrefixOrderIndex()); + assertEquals(10, child23.getPrefixOrderIndex()); + } + + @Test + public void testPrefixOrder_addEntireSubtree() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + final TestWindowContainer subtree = builder.build(); + final TestWindowContainer subtree2 = builder.build(); + + final TestWindowContainer child1 = subtree.addChildWindow(); + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child2 = subtree2.addChildWindow(); + final TestWindowContainer child3 = subtree2.addChildWindow(); + subtree.addChild(subtree2, 1); + root.addChild(subtree, 0); + + assertEquals(0, root.getPrefixOrderIndex()); + assertEquals(1, subtree.getPrefixOrderIndex()); + assertEquals(2, child1.getPrefixOrderIndex()); + assertEquals(3, child11.getPrefixOrderIndex()); + assertEquals(4, subtree2.getPrefixOrderIndex()); + assertEquals(5, child2.getPrefixOrderIndex()); + assertEquals(6, child3.getPrefixOrderIndex()); + } + + @Test + public void testPrefixOrder_remove() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + + final TestWindowContainer child1 = root.addChildWindow(); + + final TestWindowContainer child11 = child1.addChildWindow(); + final TestWindowContainer child12 = child1.addChildWindow(); + + final TestWindowContainer child2 = root.addChildWindow(); + + assertEquals(0, root.getPrefixOrderIndex()); + assertEquals(1, child1.getPrefixOrderIndex()); + assertEquals(2, child11.getPrefixOrderIndex()); + assertEquals(3, child12.getPrefixOrderIndex()); + assertEquals(4, child2.getPrefixOrderIndex()); + + root.removeChild(child1); + + assertEquals(1, child2.getPrefixOrderIndex()); + } + + /** + * Ensure children of a {@link WindowContainer} do not have + * {@link WindowContainer#onParentResize()} called when {@link WindowContainer#onParentResize()} + * is invoked with overridden bounds. + */ + @Test + public void testOnParentResizePropagation() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + + final TestWindowContainer child = root.addChildWindow(); + child.setBounds(new Rect(1,1,2,2)); + + final TestWindowContainer grandChild = mock(TestWindowContainer.class); + + child.addChildWindow(grandChild); + root.onResize(); + + // Make sure the child does not propagate resize through onParentResize when bounds are set. + verify(grandChild, never()).onParentResize(); + + child.removeChild(grandChild); + + child.setBounds(null); + child.addChildWindow(grandChild); + root.onResize(); + + // Make sure the child propagates resize through onParentResize when no bounds set. + verify(grandChild, times(1)).onParentResize(); + } + + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ + private class TestWindowContainer extends WindowContainer { + private final int mLayer; + private boolean mIsAnimating; + private boolean mIsVisible; + private boolean mFillsParent; + private Integer mOrientation; + + private boolean mOnParentSetCalled; + private boolean mOnDescendantOverrideCalled; + + /** + * Compares 2 window layers and returns -1 if the first is lesser than the second in terms + * of z-order and 1 otherwise. + */ + private final Comparator mWindowSubLayerComparator = (w1, w2) -> { + final int layer1 = w1.mLayer; + final int layer2 = w2.mLayer; + if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) { + // We insert the child window into the list ordered by the mLayer. For same layers, + // the negative one should go below others; the positive one should go above others. + return -1; + } + return 1; + }; + + TestWindowContainer(int layer, boolean isAnimating, boolean isVisible, + Integer orientation) { + super(sWm); + mLayer = layer; + mIsAnimating = isAnimating; + mIsVisible = isVisible; + mFillsParent = true; + mOrientation = orientation; + } + + TestWindowContainer getParentWindow() { + return (TestWindowContainer) getParent(); + } + + int getChildrenCount() { + return mChildren.size(); + } + + TestWindowContainer addChildWindow(TestWindowContainer child) { + addChild(child, mWindowSubLayerComparator); + return child; + } + + TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) { + TestWindowContainer child = childBuilder.build(); + addChild(child, mWindowSubLayerComparator); + return child; + } + + TestWindowContainer addChildWindow() { + return addChildWindow(new TestWindowContainerBuilder().setLayer(1)); + } + + @Override + void onParentSet() { + mOnParentSetCalled = true; + } + + @Override + void onDescendantOverrideConfigurationChanged() { + mOnDescendantOverrideCalled = true; + super.onDescendantOverrideConfigurationChanged(); + } + + @Override + boolean isSelfAnimating() { + return mIsAnimating; + } + + @Override + boolean isVisible() { + return mIsVisible; + } + + @Override + int getOrientation(int candidate) { + return mOrientation != null ? mOrientation : super.getOrientation(candidate); + } + + @Override + int getOrientation() { + return getOrientation(super.mOrientation); + } + + @Override + boolean fillsParent() { + return mFillsParent; + } + + void setFillsParent(boolean fillsParent) { + mFillsParent = fillsParent; + } + } + + private class TestWindowContainerBuilder { + private int mLayer; + private boolean mIsAnimating; + private boolean mIsVisible; + private Integer mOrientation; + + public TestWindowContainerBuilder() { + reset(); + } + + TestWindowContainerBuilder setLayer(int layer) { + mLayer = layer; + return this; + } + + TestWindowContainerBuilder setIsAnimating(boolean isAnimating) { + mIsAnimating = isAnimating; + return this; + } + + TestWindowContainerBuilder setIsVisible(boolean isVisible) { + mIsVisible = isVisible; + return this; + } + + TestWindowContainerBuilder setOrientation(int orientation) { + mOrientation = orientation; + return this; + } + + TestWindowContainerBuilder reset() { + mLayer = 0; + mIsAnimating = false; + mIsVisible = false; + mOrientation = null; + return this; + } + + TestWindowContainer build() { + return new TestWindowContainer(mLayer, mIsAnimating, mIsVisible, mOrientation); + } + } + + private class MockSurfaceBuildingContainer extends WindowContainer { + final SurfaceSession mSession = new SurfaceSession(); + + MockSurfaceBuildingContainer() { + super(sWm); + } + + class MockSurfaceBuilder extends SurfaceControl.Builder { + MockSurfaceBuilder(SurfaceSession ss) { + super(ss); + } + + @Override + public SurfaceControl build() { + return mock(SurfaceControl.class); + } + } + + @Override + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + return new MockSurfaceBuilder(mSession); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java new file mode 100644 index 000000000000..e1acd78c5bc6 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Tests for {@link WindowContainer#forAllWindows} and various implementations. + * + * Build/Install/Run: + * atest WmTests:WindowContainerTraversalTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowContainerTraversalTests extends WindowTestsBase { + + @Test + public void testDockedDividerPosition() throws Exception { + final WindowState splitScreenWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + mDisplayContent, "splitScreenWindow"); + final WindowState splitScreenSecondaryWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); + + sWm.mInputMethodTarget = splitScreenWindow; + + Consumer c = mock(Consumer.class); + mDisplayContent.forAllWindows(c, false); + + verify(c).accept(eq(mDockedDividerWindow)); + verify(c).accept(eq(mImeWindow)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java new file mode 100644 index 000000000000..6a00de473a85 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.DisplayCutout.fromBoundingRect; +import static android.view.WindowManager.LayoutParams.FILL_PARENT; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.ActivityManager.TaskDescription; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.IWindow; +import android.view.WindowManager; + +import com.android.server.wm.utils.WmDisplayCutout; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery. + * + * Build/Install/Run: + * atest WmTests:WindowFrameTests + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowFrameTests extends WindowTestsBase { + + private WindowToken mWindowToken; + private final IWindow mIWindow = new TestIWindow(); + + class WindowStateWithTask extends WindowState { + final Task mTask; + boolean mDockedResizingForTest = false; + WindowStateWithTask(WindowManager.LayoutParams attrs, Task t) { + super(sWm, null, mIWindow, mWindowToken, null, 0, 0, attrs, 0, 0, + false /* ownerCanAddInternalSystemWindow */); + mTask = t; + } + + @Override + Task getTask() { + return mTask; + } + + @Override + boolean isDockedResizing() { + return mDockedResizingForTest; + } + }; + + class TaskWithBounds extends Task { + final Rect mBounds; + final Rect mInsetBounds = new Rect(); + boolean mFullscreenForTest = true; + TaskWithBounds(Rect bounds) { + super(0, mStubStack, 0, sWm, 0, false, new TaskDescription(), null); + mBounds = bounds; + setBounds(bounds); + } + + @Override + public Rect getBounds() { + return mBounds; + } + + @Override + public void getBounds(Rect out) { + out.set(mBounds); + } + + @Override + public void getOverrideBounds(Rect outBounds) { + outBounds.set(mBounds); + } + @Override + void getTempInsetBounds(Rect outBounds) { + outBounds.set(mInsetBounds); + } + @Override + boolean isFullscreen() { + return mFullscreenForTest; + } + } + + TaskStack mStubStack; + + @Before + public void setUp() throws Exception { + super.setUp(); + + // Just any non zero value. + sWm.mSystemDecorLayer = 10000; + + mWindowToken = WindowTestUtils.createTestAppWindowToken( + sWm.getDefaultDisplayContentLocked()); + mStubStack = new TaskStack(sWm, 0, null); + } + + public void assertRect(Rect rect, int left, int top, int right, int bottom) { + assertEquals(left, rect.left); + assertEquals(top, rect.top); + assertEquals(right, rect.right); + assertEquals(bottom, rect.bottom); + } + + @Test + public void testLayoutInFullscreenTaskInsets() throws Exception { + Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame + WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + final int bottomContentInset = 100; + final int topContentInset = 50; + final int bottomVisibleInset = 30; + final int topVisibleInset = 70; + final int leftStableInset = 20; + final int rightStableInset = 90; + + // With no insets or system decor all the frames incoming from PhoneWindowManager + // are identical. + final Rect pf = new Rect(0, 0, 1000, 1000); + final Rect df = pf; + final Rect of = df; + final Rect cf = new Rect(pf); + // Produce some insets + cf.top += 50; + cf.bottom -= 100; + final Rect vf = new Rect(pf); + vf.top += topVisibleInset; + vf.bottom -= bottomVisibleInset; + final Rect sf = new Rect(pf); + sf.left += leftStableInset; + sf.right -= rightStableInset; + + final Rect dcf = pf; + // When mFrame extends past cf, the content insets are + // the difference between mFrame and ContentFrame. Visible + // and stable frames work the same way. + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame,0, 0, 1000, 1000); + assertRect(w.mContentInsets, 0, topContentInset, 0, bottomContentInset); + assertRect(w.mVisibleInsets, 0, topVisibleInset, 0, bottomVisibleInset); + assertRect(w.mStableInsets, leftStableInset, 0, rightStableInset, 0); + // The frames remain as passed in shrunk to the window frame + assertTrue(cf.equals(w.getContentFrameLw())); + assertTrue(vf.equals(w.getVisibleFrameLw())); + assertTrue(sf.equals(w.getStableFrameLw())); + // On the other hand mFrame doesn't extend past cf we won't get any insets + w.mAttrs.x = 100; + w.mAttrs.y = 100; + w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT + w.mRequestedWidth = 100; + w.mRequestedHeight = 100; + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 100, 100, 200, 200); + assertRect(w.mContentInsets, 0, 0, 0, 0); + // In this case the frames are shrunk to the window frame. + assertTrue(w.mFrame.equals(w.getContentFrameLw())); + assertTrue(w.mFrame.equals(w.getVisibleFrameLw())); + assertTrue(w.mFrame.equals(w.getStableFrameLw())); + } + + @Test + public void testLayoutInFullscreenTaskNoInsets() throws Exception { + Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame + WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + // With no insets or system decor all the frames incoming from PhoneWindowManager + // are identical. + final Rect pf = new Rect(0, 0, 1000, 1000); + + // Here the window has FILL_PARENT, FILL_PARENT + // so we expect it to fill the entire available frame. + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 0, 0, 1000, 1000); + + // It can select various widths and heights within the bounds. + // Strangely the window attribute width is ignored for normal windows + // and we use mRequestedWidth/mRequestedHeight + w.mAttrs.width = 300; + w.mAttrs.height = 300; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + // Explicit width and height without requested width/height + // gets us nothing. + assertRect(w.mFrame, 0, 0, 0, 0); + + w.mRequestedWidth = 300; + w.mRequestedHeight = 300; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + // With requestedWidth/Height we can freely choose our size within the + // parent bounds. + assertRect(w.mFrame, 0, 0, 300, 300); + + // With FLAG_SCALED though, requestedWidth/height is used to control + // the unscaled surface size, and mAttrs.width/height becomes the + // layout controller. + w.mAttrs.flags = WindowManager.LayoutParams.FLAG_SCALED; + w.mRequestedHeight = -1; + w.mRequestedWidth = -1; + w.mAttrs.width = 100; + w.mAttrs.height = 100; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 0, 0, 100, 100); + w.mAttrs.flags = 0; + + // But sizes too large will be clipped to the containing frame + w.mRequestedWidth = 1200; + w.mRequestedHeight = 1200; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 0, 0, 1000, 1000); + + // Before they are clipped though windows will be shifted + w.mAttrs.x = 300; + w.mAttrs.y = 300; + w.mRequestedWidth = 1000; + w.mRequestedHeight = 1000; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 0, 0, 1000, 1000); + + // If there is room to move around in the parent frame the window will be shifted according + // to gravity. + w.mAttrs.x = 0; + w.mAttrs.y = 0; + w.mRequestedWidth = 300; + w.mRequestedHeight = 300; + w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 700, 0, 1000, 300); + w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 700, 700, 1000, 1000); + // Window specified x and y are interpreted as offsets in the opposite + // direction of gravity + w.mAttrs.x = 100; + w.mAttrs.y = 100; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, 600, 600, 900, 900); + } + + @Test + public void testLayoutNonfullscreenTask() { + final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo(); + final int logicalWidth = displayInfo.logicalWidth; + final int logicalHeight = displayInfo.logicalHeight; + + final int taskLeft = logicalWidth / 4; + final int taskTop = logicalHeight / 4; + final int taskRight = logicalWidth / 4 * 3; + final int taskBottom = logicalHeight / 4 * 3; + final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom); + TaskWithBounds task = new TaskWithBounds(taskBounds); + task.mFullscreenForTest = false; + WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null, WmDisplayCutout.NO_CUTOUT, false); + // For non fullscreen tasks the containing frame is based off the + // task bounds not the parent frame. + assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); + assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom); + assertRect(w.mContentInsets, 0, 0, 0, 0); + + pf.set(0, 0, logicalWidth, logicalHeight); + // We still produce insets against the containing frame the same way. + final int cfRight = logicalWidth / 2; + final int cfBottom = logicalHeight / 2; + final Rect cf = new Rect(0, 0, cfRight, cfBottom); + w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); + int contentInsetRight = taskRight - cfRight; + int contentInsetBottom = taskBottom - cfBottom; + assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom); + assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight, + taskBottom - contentInsetBottom); + + pf.set(0, 0, logicalWidth, logicalHeight); + // However if we set temp inset bounds, the insets will be computed + // as if our window was laid out there, but it will be laid out according to + // the task bounds. + final int insetLeft = logicalWidth / 5; + final int insetTop = logicalHeight / 5; + final int insetRight = insetLeft + (taskRight - taskLeft); + final int insetBottom = insetTop + (taskBottom - taskTop); + task.mInsetBounds.set(insetLeft, insetTop, insetRight, insetBottom); + w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null, WmDisplayCutout.NO_CUTOUT, false); + assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); + contentInsetRight = insetRight - cfRight; + contentInsetBottom = insetBottom - cfBottom; + assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom); + assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight, + taskBottom - contentInsetBottom); + } + + @Test + public void testCalculatePolicyCrop() { + final WindowStateWithTask w = createWindow( + new TaskWithBounds(null), FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo(); + final int logicalWidth = displayInfo.logicalWidth; + final int logicalHeight = displayInfo.logicalHeight; + final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); + final Rect df = pf; + final Rect of = df; + final Rect cf = new Rect(pf); + // Produce some insets + cf.top += displayInfo.logicalWidth / 10; + cf.bottom -= displayInfo.logicalWidth / 5; + final Rect vf = cf; + final Rect sf = vf; + // We use a decor content frame with insets to produce cropping. + Rect dcf = cf; + + final Rect policyCrop = new Rect(); + + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); + w.calculatePolicyCrop(policyCrop); + assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom); + + dcf.setEmpty(); + // Likewise with no decor frame we would get no crop + w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null, WmDisplayCutout.NO_CUTOUT, false); + w.calculatePolicyCrop(policyCrop); + assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight); + + // Now we set up a window which doesn't fill the entire decor frame. + // Normally it would be cropped to it's frame but in the case of docked resizing + // we need to account for the fact the windows surface will be made + // fullscreen and thus also make the crop fullscreen. + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + w.mAttrs.width = logicalWidth / 2; + w.mAttrs.height = logicalHeight / 2; + w.mRequestedWidth = logicalWidth / 2; + w.mRequestedHeight = logicalHeight / 2; + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, WmDisplayCutout.NO_CUTOUT, false); + + w.calculatePolicyCrop(policyCrop); + // Normally the crop is shrunk from the decor frame + // to the computed window frame. + assertRect(policyCrop, 0, 0, logicalWidth / 2, logicalHeight / 2); + + w.mDockedResizingForTest = true; + w.calculatePolicyCrop(policyCrop); + // But if we are docked resizing it won't be, however we will still be + // shrunk to the decor frame and the display. + assertRect(policyCrop, 0, 0, + Math.min(pf.width(), displayInfo.logicalWidth), + Math.min(pf.height(), displayInfo.logicalHeight)); + } + + @Test + public void testLayoutLetterboxedWindow() { + // First verify task behavior in multi-window mode. + final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo(); + final int logicalWidth = displayInfo.logicalWidth; + final int logicalHeight = displayInfo.logicalHeight; + + final int taskLeft = logicalWidth / 5; + final int taskTop = logicalHeight / 5; + final int taskRight = logicalWidth / 4 * 3; + final int taskBottom = logicalHeight / 4 * 3; + final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom); + TaskWithBounds task = new TaskWithBounds(taskBounds); + task.mInsetBounds.set(taskLeft, taskTop, taskRight, taskBottom); + task.mFullscreenForTest = false; + WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); + w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, + pf /* contentFrame */, pf /* visibleFrame */, pf /* decorFrame */, + pf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT, false); + // For non fullscreen tasks the containing frame is based off the + // task bounds not the parent frame. + assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); + assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom); + assertRect(w.mContentInsets, 0, 0, 0, 0); + + // Now simulate switch to fullscreen for letterboxed app. + final int xInset = logicalWidth / 10; + final int yInset = logicalWidth / 10; + final Rect cf = new Rect(xInset, yInset, logicalWidth - xInset, logicalHeight - yInset); + Configuration config = new Configuration(w.mAppToken.getOverrideConfiguration()); + config.windowConfiguration.setBounds(cf); + w.mAppToken.onOverrideConfigurationChanged(config); + pf.set(0, 0, logicalWidth, logicalHeight); + task.mFullscreenForTest = true; + + w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, + cf /* contentFrame */, cf /* visibleFrame */, pf /* decorFrame */, + cf /* stableFrame */, null /* outsetFrame */, WmDisplayCutout.NO_CUTOUT, false); + assertEquals(cf, w.mFrame); + assertEquals(cf, w.getContentFrameLw()); + assertRect(w.mContentInsets, 0, 0, 0, 0); + } + + @Test + public void testDisplayCutout() { + // Regular fullscreen task and window + Task task = new TaskWithBounds(null); + WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + final Rect pf = new Rect(0, 0, 1000, 2000); + // Create a display cutout of size 50x50, aligned top-center + final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); + + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false); + + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0); + } + + @Test + public void testDisplayCutout_tempInsetBounds() { + // Regular fullscreen task and window + TaskWithBounds task = new TaskWithBounds(new Rect(0, -500, 1000, 1500)); + task.mFullscreenForTest = false; + task.mInsetBounds.set(0, 0, 1000, 2000); + WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); + w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; + + final Rect pf = new Rect(0, -500, 1000, 1500); + // Create a display cutout of size 50x50, aligned top-center + final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height()); + + w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false); + + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0); + assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0); + } + + private WindowStateWithTask createWindow(Task task, int width, int height) { + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION); + attrs.width = width; + attrs.height = height; + + return new WindowStateWithTask(attrs, task); + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java new file mode 100644 index 000000000000..d91079e3bb1b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRule.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.app.ActivityManagerInternal; +import android.content.Context; +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; +import android.support.test.InstrumentationRegistry; +import android.view.InputChannel; + +import com.android.server.LocalServices; +import com.android.server.input.InputManagerService; +import com.android.server.policy.WindowManagerPolicy; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.mockito.invocation.InvocationOnMock; + +/** + * A test rule that sets up a fresh WindowManagerService instance before each test and makes sure + * to properly tear it down after. + * + *

+ * Usage: + *

+ * {@literal @}Rule
+ *  public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
+ * 
+ */ +public class WindowManagerServiceRule implements TestRule { + + private WindowManagerService mService; + private TestWindowManagerPolicy mPolicy; + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + runWithDexmakerShareClassLoader(this::setUp); + try { + base.evaluate(); + } finally { + tearDown(); + } + } + + private void setUp() { + final Context context = InstrumentationRegistry.getTargetContext(); + + removeServices(); + + LocalServices.addService(DisplayManagerInternal.class, + mock(DisplayManagerInternal.class)); + + LocalServices.addService(PowerManagerInternal.class, + mock(PowerManagerInternal.class)); + final PowerManagerInternal pm = + LocalServices.getService(PowerManagerInternal.class); + PowerSaveState state = new PowerSaveState.Builder().build(); + doReturn(state).when(pm).getLowPowerState(anyInt()); + + LocalServices.addService(ActivityManagerInternal.class, + mock(ActivityManagerInternal.class)); + final ActivityManagerInternal am = + LocalServices.getService(ActivityManagerInternal.class); + doAnswer((InvocationOnMock invocationOnMock) -> { + final Runnable runnable = invocationOnMock.getArgument(0); + if (runnable != null) { + runnable.run(); + } + return null; + }).when(am).notifyKeyguardFlagsChanged(any()); + + InputManagerService ims = mock(InputManagerService.class); + // InputChannel is final and can't be mocked. + InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM); + if (input != null && input.length > 1) { + doReturn(input[1]).when(ims).monitorInput(anyString()); + } + + mService = WindowManagerService.main(context, ims, true, false, + false, mPolicy = new TestWindowManagerPolicy( + WindowManagerServiceRule.this::getWindowManagerService)); + + mService.onInitReady(); + + // Display creation is driven by the ActivityManagerService via ActivityStackSupervisor. + // We emulate those steps here. + mService.mRoot.createDisplayContent( + mService.mDisplayManager.getDisplay(DEFAULT_DISPLAY), + mock(DisplayWindowController.class)); + } + + private void removeServices() { + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.removeServiceForTest(PowerManagerInternal.class); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerPolicy.class); + } + + private void tearDown() { + waitUntilWindowManagerHandlersIdle(); + removeServices(); + mService = null; + mPolicy = null; + } + }; + } + + public WindowManagerService getWindowManagerService() { + return mService; + } + + public TestWindowManagerPolicy getWindowManagerPolicy() { + return mPolicy; + } + + public void waitUntilWindowManagerHandlersIdle() { + final WindowManagerService wm = getWindowManagerService(); + if (wm != null) { + wm.mH.runWithScissors(() -> { }, 0); + wm.mAnimationHandler.runWithScissors(() -> { }, 0); + SurfaceAnimationThread.getHandler().runWithScissors(() -> { }, 0); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java new file mode 100644 index 000000000000..6cf6d7bc96ec --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceRuleTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@Presubmit +@SmallTest +public class WindowManagerServiceRuleTest { + + @Rule + public final WindowManagerServiceRule mRule = new WindowManagerServiceRule(); + + @Test + public void testWindowManagerSetUp() { + assertThat(mRule.getWindowManagerService(), notNullValue()); + } +} \ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java new file mode 100644 index 000000000000..0f07e7eb3090 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.WindowManager; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.LinkedList; + +/** + * Tests for the {@link WindowState} class. + * + * Build/Install/Run: + * atest WmTests:WindowStateTests + */ +@SmallTest +@FlakyTest(bugId = 74078662) +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowStateTests extends WindowTestsBase { + + @Test + public void testIsParentWindowHidden() throws Exception { + final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); + final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); + + // parentWindow is initially set to hidden. + assertTrue(parentWindow.mHidden); + assertFalse(parentWindow.isParentWindowHidden()); + assertTrue(child1.isParentWindowHidden()); + assertTrue(child2.isParentWindowHidden()); + + parentWindow.mHidden = false; + assertFalse(parentWindow.isParentWindowHidden()); + assertFalse(child1.isParentWindowHidden()); + assertFalse(child2.isParentWindowHidden()); + + } + + @Test + public void testIsChildWindow() throws Exception { + final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); + final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); + final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow"); + + assertFalse(parentWindow.isChildWindow()); + assertTrue(child1.isChildWindow()); + assertTrue(child2.isChildWindow()); + assertFalse(randomWindow.isChildWindow()); + } + + @Test + public void testHasChild() throws Exception { + final WindowState win1 = createWindow(null, TYPE_APPLICATION, "win1"); + final WindowState win11 = createWindow(win1, FIRST_SUB_WINDOW, "win11"); + final WindowState win12 = createWindow(win1, FIRST_SUB_WINDOW, "win12"); + final WindowState win2 = createWindow(null, TYPE_APPLICATION, "win2"); + final WindowState win21 = createWindow(win2, FIRST_SUB_WINDOW, "win21"); + final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow"); + + assertTrue(win1.hasChild(win11)); + assertTrue(win1.hasChild(win12)); + assertTrue(win2.hasChild(win21)); + + assertFalse(win1.hasChild(win21)); + assertFalse(win1.hasChild(randomWindow)); + + assertFalse(win2.hasChild(win11)); + assertFalse(win2.hasChild(win12)); + assertFalse(win2.hasChild(randomWindow)); + } + + @Test + public void testGetParentWindow() throws Exception { + final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); + final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); + final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); + + assertNull(parentWindow.getParentWindow()); + assertEquals(parentWindow, child1.getParentWindow()); + assertEquals(parentWindow, child2.getParentWindow()); + } + + @Test + public void testOverlayWindowHiddenWhenSuspended() { + final WindowState overlayWindow = spy(createWindow(null, TYPE_APPLICATION_OVERLAY, + "overlayWindow")); + overlayWindow.setHiddenWhileSuspended(true); + verify(overlayWindow).hideLw(true, true); + overlayWindow.setHiddenWhileSuspended(false); + verify(overlayWindow).showLw(true, true); + } + + @Test + public void testGetTopParentWindow() throws Exception { + final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); + final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); + final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); + + assertEquals(root, root.getTopParentWindow()); + assertEquals(root, child1.getTopParentWindow()); + assertEquals(child1, child2.getParentWindow()); + assertEquals(root, child2.getTopParentWindow()); + + // Test case were child is detached from parent. + root.removeChild(child1); + assertEquals(child1, child1.getTopParentWindow()); + assertEquals(child1, child2.getParentWindow()); + } + + @Test + public void testIsOnScreen_hiddenByPolicy() { + final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); + window.setHasSurface(true); + assertTrue(window.isOnScreen()); + window.hideLw(false /* doAnimation */); + assertFalse(window.isOnScreen()); + } + + @Test + public void testCanBeImeTarget() throws Exception { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow"); + + // Setting FLAG_NOT_FOCUSABLE without FLAG_ALT_FOCUSABLE_IM prevents the window from being + // an IME target. + appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + + // Make windows visible + appWindow.setHasSurface(true); + imeWindow.setHasSurface(true); + + // Windows without flags (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM) can't be IME targets + assertFalse(appWindow.canBeImeTarget()); + assertFalse(imeWindow.canBeImeTarget()); + + // Add IME target flags + appWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM); + imeWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM); + + // Visible app window with flags can be IME target while an IME window can never be an IME + // target regardless of its visibility or flags. + assertTrue(appWindow.canBeImeTarget()); + assertFalse(imeWindow.canBeImeTarget()); + + // Make windows invisible + appWindow.hideLw(false /* doAnimation */); + imeWindow.hideLw(false /* doAnimation */); + + // Invisible window can't be IME targets even if they have the right flags. + assertFalse(appWindow.canBeImeTarget()); + assertFalse(imeWindow.canBeImeTarget()); + } + + @Test + public void testGetWindow() throws Exception { + final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); + final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild"); + final WindowState mediaOverlayChild = createWindow(root, + TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild"); + final WindowState attachedDialogChild = createWindow(root, + TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild"); + final WindowState subPanelChild = createWindow(root, + TYPE_APPLICATION_SUB_PANEL, "subPanelChild"); + final WindowState aboveSubPanelChild = createWindow(root, + TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild"); + + final LinkedList windows = new LinkedList(); + + root.getWindow(w -> { + windows.addLast(w); + return false; + }); + + // getWindow should have returned candidate windows in z-order. + assertEquals(aboveSubPanelChild, windows.pollFirst()); + assertEquals(subPanelChild, windows.pollFirst()); + assertEquals(attachedDialogChild, windows.pollFirst()); + assertEquals(root, windows.pollFirst()); + assertEquals(mediaOverlayChild, windows.pollFirst()); + assertEquals(mediaChild, windows.pollFirst()); + assertTrue(windows.isEmpty()); + } + + @Test + public void testPrepareWindowToDisplayDuringRelayout() throws Exception { + testPrepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + testPrepareWindowToDisplayDuringRelayout(true /*wasVisible*/); + + // Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON + // before calling prepareWindowToDisplayDuringRelayout for windows with flag in the same + // appWindowToken. + final AppWindowToken appWindowToken = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState first = createWindow(null, TYPE_APPLICATION, appWindowToken, "first"); + final WindowState second = createWindow(null, TYPE_APPLICATION, appWindowToken, "second"); + second.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; + + reset(mPowerManagerWrapper); + first.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + verify(mPowerManagerWrapper, never()).wakeUp(anyLong(), anyString()); + assertTrue(appWindowToken.canTurnScreenOn()); + + reset(mPowerManagerWrapper); + second.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); + assertFalse(appWindowToken.canTurnScreenOn()); + + // Call prepareWindowToDisplayDuringRelayout for two window that have FLAG_TURN_SCREEN_ON + // from the same appWindowToken. Only one should trigger the wakeup. + appWindowToken.setCanTurnScreenOn(true); + first.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; + second.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; + + reset(mPowerManagerWrapper); + first.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); + assertFalse(appWindowToken.canTurnScreenOn()); + + reset(mPowerManagerWrapper); + second.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + verify(mPowerManagerWrapper, never()).wakeUp(anyLong(), anyString()); + assertFalse(appWindowToken.canTurnScreenOn()); + + // Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an + // appWindowToken. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup + final WindowToken windowToken = WindowTestUtils.createTestWindowToken(FIRST_SUB_WINDOW, + mDisplayContent); + final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken, + "firstWindow"); + final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken, + "secondWindow"); + firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; + secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; + + reset(mPowerManagerWrapper); + firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); + + reset(mPowerManagerWrapper); + secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/); + verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); + } + + @Test + public void testCanAffectSystemUiFlags() throws Exception { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mToken.setHidden(false); + assertTrue(app.canAffectSystemUiFlags()); + app.mToken.setHidden(true); + assertFalse(app.canAffectSystemUiFlags()); + app.mToken.setHidden(false); + app.mAttrs.alpha = 0.0f; + assertFalse(app.canAffectSystemUiFlags()); + + } + + @Test + public void testCanAffectSystemUiFlags_disallow() throws Exception { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mToken.setHidden(false); + assertTrue(app.canAffectSystemUiFlags()); + app.getTask().setCanAffectSystemUiFlags(false); + assertFalse(app.canAffectSystemUiFlags()); + } + + @Test + public void testIsSelfOrAncestorWindowAnimating() throws Exception { + final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); + final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); + final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); + assertFalse(child2.isSelfOrAncestorWindowAnimatingExit()); + child2.mAnimatingExit = true; + assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); + child2.mAnimatingExit = false; + root.mAnimatingExit = true; + assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); + } + + @Test + public void testLayoutSeqResetOnReparent() throws Exception { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mLayoutSeq = 1; + mDisplayContent.mLayoutSeq = 1; + + app.onDisplayChanged(mDisplayContent); + + assertThat(app.mLayoutSeq, not(is(mDisplayContent.mLayoutSeq))); + } + + @Test + public void testDisplayIdUpdatedOnReparent() throws Exception { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + // fake a different display + app.mInputWindowHandle.displayId = mDisplayContent.getDisplayId() + 1; + app.onDisplayChanged(mDisplayContent); + + assertThat(app.mInputWindowHandle.displayId, is(mDisplayContent.getDisplayId())); + assertThat(app.getDisplayId(), is(mDisplayContent.getDisplayId())); + } + + private void testPrepareWindowToDisplayDuringRelayout(boolean wasVisible) { + reset(mPowerManagerWrapper); + final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); + root.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; + + root.prepareWindowToDisplayDuringRelayout(wasVisible /*wasVisible*/); + verify(mPowerManagerWrapper).wakeUp(anyLong(), anyString()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowSurfacePlacerTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowSurfacePlacerTest.java new file mode 100644 index 000000000000..13f791634e4f --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowSurfacePlacerTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_TASK_OPEN; + +import static junit.framework.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.WindowManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link WindowSurfacePlacer}. + * + * Build/Install/Run: + * atest WmTests:WindowSurfacePlacerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowSurfacePlacerTest extends WindowTestsBase { + + private WindowSurfacePlacer mWindowSurfacePlacer; + + @Before + public void setUp() throws Exception { + super.setUp(); + mWindowSurfacePlacer = new WindowSurfacePlacer(sWm); + } + + @Test + public void testTranslucentOpen() throws Exception { + synchronized (sWm.mWindowMap) { + final AppWindowToken behind = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final AppWindowToken translucentOpening = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + translucentOpening.setFillsParent(false); + translucentOpening.setHidden(true); + sWm.mOpeningApps.add(behind); + sWm.mOpeningApps.add(translucentOpening); + assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN, + mWindowSurfacePlacer.maybeUpdateTransitToTranslucentAnim(TRANSIT_TASK_OPEN)); + } + } + + @Test + public void testTranslucentClose() throws Exception { + synchronized (sWm.mWindowMap) { + final AppWindowToken behind = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final AppWindowToken translucentClosing = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + translucentClosing.setFillsParent(false); + sWm.mClosingApps.add(translucentClosing); + assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE, + mWindowSurfacePlacer.maybeUpdateTransitToTranslucentAnim(TRANSIT_TASK_CLOSE)); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java new file mode 100644 index 000000000000..6ab9c3010dd5 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.AppOpsManager.OP_NONE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + +import static com.android.server.wm.WindowContainer.POSITION_TOP; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyFloat; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.IBinder; +import android.view.IApplicationToken; +import android.view.IWindow; +import android.view.WindowManager; + +import org.mockito.invocation.InvocationOnMock; + +/** + * A collection of static functions that can be referenced by other test packages to provide access + * to WindowManager related test functionality. + */ +public class WindowTestUtils { + public static int sNextTaskId = 0; + + /** + * Retrieves an instance of a mock {@link WindowManagerService}. + */ + public static WindowManagerService getMockWindowManagerService() { + final WindowManagerService service = mock(WindowManagerService.class); + final WindowHashMap windowMap = new WindowHashMap(); + when(service.getWindowManagerLock()).thenReturn(windowMap); + return service; + } + + /** + * Creates a mock instance of {@link StackWindowController}. + */ + public static StackWindowController createMockStackWindowContainerController() { + StackWindowController controller = mock(StackWindowController.class); + controller.mContainer = mock(TestTaskStack.class); + + // many components rely on the {@link StackWindowController#adjustConfigurationForBounds} + // to properly set bounds values in the configuration. We must mimick those actions here. + doAnswer((InvocationOnMock invocationOnMock) -> { + final Configuration config = invocationOnMock.getArgument(7); + final Rect bounds = invocationOnMock.getArgument(0); + config.windowConfiguration.setBounds(bounds); + return null; + }).when(controller).adjustConfigurationForBounds(any(), any(), any(), any(), + anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt()); + + return controller; + } + + /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */ + public static Task createTaskInStack(WindowManagerService service, TaskStack stack, + int userId) { + synchronized (service.mWindowMap) { + final Task newTask = new Task(sNextTaskId++, stack, userId, service, 0, false, + new ActivityManager.TaskDescription(), null); + stack.addTask(newTask, POSITION_TOP); + return newTask; + } + } + + /** + * An extension of {@link TestTaskStack}, which overrides package scoped methods that would not + * normally be mocked out. + */ + public static class TestTaskStack extends TaskStack { + TestTaskStack(WindowManagerService service, int stackId) { + super(service, stackId, null); + } + + @Override + void addTask(Task task, int position, boolean showForAllUsers, boolean moveParents) { + // Do nothing. + } + } + + static TestAppWindowToken createTestAppWindowToken(DisplayContent dc) { + synchronized (dc.mService.mWindowMap) { + return new TestAppWindowToken(dc); + } + } + + /** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */ + public static class TestAppWindowToken extends AppWindowToken { + boolean mOnTop = false; + + private TestAppWindowToken(DisplayContent dc) { + super(dc.mService, new IApplicationToken.Stub() { + public String getName() {return null;} + }, false, dc, true /* fillsParent */); + } + + TestAppWindowToken(WindowManagerService service, IApplicationToken token, + boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, + boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, + int rotationAnimationHint, int configChanges, boolean launchTaskBehind, + boolean alwaysFocusable, AppWindowContainerController controller) { + super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen, + showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges, + launchTaskBehind, alwaysFocusable, controller); + } + + int getWindowsCount() { + return mChildren.size(); + } + + boolean hasWindow(WindowState w) { + return mChildren.contains(w); + } + + WindowState getFirstChild() { + return mChildren.peekFirst(); + } + + WindowState getLastChild() { + return mChildren.peekLast(); + } + + int positionInParent() { + return getParent().mChildren.indexOf(this); + } + + void setIsOnTop(boolean onTop) { + mOnTop = onTop; + } + + @Override + boolean isOnTop() { + return mOnTop; + } + } + + static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { + return createTestWindowToken(type, dc, false /* persistOnEmpty */); + } + + static TestWindowToken createTestWindowToken(int type, DisplayContent dc, + boolean persistOnEmpty) { + synchronized (dc.mService.mWindowMap) { + return new TestWindowToken(type, dc, persistOnEmpty); + } + } + + /* Used so we can gain access to some protected members of the {@link WindowToken} class */ + public static class TestWindowToken extends WindowToken { + + private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) { + super(dc.mService, mock(IBinder.class), type, persistOnEmpty, dc, + false /* ownerCanManageAppTokens */); + } + + int getWindowsCount() { + return mChildren.size(); + } + + boolean hasWindow(WindowState w) { + return mChildren.contains(w); + } + } + + /* Used so we can gain access to some protected members of the {@link Task} class */ + public static class TestTask extends Task { + boolean mShouldDeferRemoval = false; + boolean mOnDisplayChangedCalled = false; + private boolean mIsAnimating = false; + + TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, + int resizeMode, boolean supportsPictureInPicture, + TaskWindowContainerController controller) { + super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture, + new ActivityManager.TaskDescription(), controller); + } + + boolean shouldDeferRemoval() { + return mShouldDeferRemoval; + } + + int positionInParent() { + return getParent().mChildren.indexOf(this); + } + + @Override + void onDisplayChanged(DisplayContent dc) { + super.onDisplayChanged(dc); + mOnDisplayChangedCalled = true; + } + + @Override + boolean isSelfAnimating() { + return mIsAnimating; + } + + void setLocalIsAnimating(boolean isAnimating) { + mIsAnimating = isAnimating; + } + } + + /** + * Used so we can gain access to some protected members of {@link TaskWindowContainerController} + * class. + */ + public static class TestTaskWindowContainerController extends TaskWindowContainerController { + + TestTaskWindowContainerController(WindowTestsBase testsBase) { + this(testsBase.createStackControllerOnDisplay(testsBase.mDisplayContent)); + } + + TestTaskWindowContainerController(StackWindowController stackController) { + super(sNextTaskId++, new TaskWindowContainerListener() { + @Override + public void registerConfigurationChangeListener( + ConfigurationContainerListener listener) { + + } + + @Override + public void unregisterConfigurationChangeListener( + ConfigurationContainerListener listener) { + + } + + @Override + public void onSnapshotChanged(ActivityManager.TaskSnapshot snapshot) { + + } + + @Override + public void requestResize(Rect bounds, int resizeMode) { + + } + }, stackController, 0 /* userId */, null /* bounds */, RESIZE_MODE_UNRESIZEABLE, + false /* supportsPictureInPicture */, true /* toTop*/, + true /* showForAllUsers */, new ActivityManager.TaskDescription(), + stackController.mService); + } + + @Override + TestTask createTask(int taskId, TaskStack stack, int userId, int resizeMode, + boolean supportsPictureInPicture, ActivityManager.TaskDescription taskDescription) { + return new TestTask(taskId, stack, userId, mService, resizeMode, + supportsPictureInPicture, this); + } + } + + public static class TestAppWindowContainerController extends AppWindowContainerController { + + final IApplicationToken mToken; + + TestAppWindowContainerController(TestTaskWindowContainerController taskController) { + this(taskController, new TestIApplicationToken()); + } + + TestAppWindowContainerController(TestTaskWindowContainerController taskController, + IApplicationToken token) { + super(taskController, token, null /* listener */, 0 /* index */, + SCREEN_ORIENTATION_UNSPECIFIED, true /* fullscreen */, + true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */, + false /* launchTaskBehind */, false /* alwaysFocusable */, + 0 /* targetSdkVersion */, 0 /* rotationAnimationHint */, + 0 /* inputDispatchingTimeoutNanos */, taskController.mService); + mToken = token; + } + + @Override + AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token, + boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, + boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, + int rotationAnimationHint, int configChanges, boolean launchTaskBehind, + boolean alwaysFocusable, AppWindowContainerController controller) { + return new TestAppWindowToken(service, token, voiceInteraction, dc, + inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, + orientation, + rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, + controller); + } + + AppWindowToken getAppWindowToken(DisplayContent dc) { + return (AppWindowToken) dc.getWindowToken(mToken.asBinder()); + } + } + + public static class TestIApplicationToken implements IApplicationToken { + + private final Binder mBinder = new Binder(); + @Override + public IBinder asBinder() { + return mBinder; + } + @Override + public String getName() { + return null; + } + } + + /** Used to track resize reports. */ + public static class TestWindowState extends WindowState { + boolean resizeReported; + + TestWindowState(WindowManagerService service, Session session, IWindow window, + WindowManager.LayoutParams attrs, WindowToken token) { + super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, + false /* ownerCanAddInternalSystemWindow */); + } + + @Override + void reportResized() { + super.reportResized(); + resizeReported = true; + } + + @Override + public boolean isGoneForLayoutLw() { + return false; + } + + @Override + void updateResizingWindowIfNeeded() { + // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive + // the system that it can actually update the window. + boolean hadSurface = mHasSurface; + mHasSurface = true; + + super.updateResizingWindowIfNeeded(); + + mHasSurface = hadSurface; + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java new file mode 100644 index 000000000000..473a287e3d9c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.View.VISIBLE; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; +import android.testing.DexmakerShareClassLoaderRule; +import android.util.Log; +import android.view.Display; +import android.view.DisplayInfo; +import org.junit.Assert; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.view.IWindow; +import android.view.WindowManager; + +import static android.app.AppOpsManager.OP_NONE; +import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static org.mockito.Mockito.mock; + +import com.android.server.AttributeCache; + +import java.util.HashSet; +import java.util.LinkedList; + +/** + * Common base class for window manager unit test classes. + * + * Make sure any requests to WM hold the WM lock if needed b/73966377 + */ +class WindowTestsBase { + private static final String TAG = WindowTestsBase.class.getSimpleName(); + WindowManagerService sWm = null; // TODO(roosa): rename to mWm in follow-up CL + private final IWindow mIWindow = new TestIWindow(); + private Session mMockSession; + // The default display is removed in {@link #setUp} and then we iterate over all displays to + // make sure we don't collide with any existing display. If we run into no other display, the + // added display should be treated as default. This cannot be the default display + private static int sNextDisplayId = DEFAULT_DISPLAY + 1; + static int sNextStackId = 1000; + + DisplayContent mDisplayContent; + DisplayInfo mDisplayInfo = new DisplayInfo(); + WindowState mWallpaperWindow; + WindowState mImeWindow; + WindowState mImeDialogWindow; + WindowState mStatusBarWindow; + WindowState mDockedDividerWindow; + WindowState mNavBarWindow; + WindowState mAppWindow; + WindowState mChildAppWindowAbove; + WindowState mChildAppWindowBelow; + HashSet mCommonWindows; + WallpaperController mWallpaperController; + + @Rule + public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = + new DexmakerShareClassLoaderRule(); + + @Rule + public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule(); + + static WindowState.PowerManagerWrapper mPowerManagerWrapper; // TODO(roosa): make non-static. + + @Before + public void setUp() throws Exception { + // If @Before throws an exception, the error isn't logged. This will make sure any failures + // in the set up are clear. This can be removed when b/37850063 is fixed. + try { + mMockSession = mock(Session.class); + mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class); + + final Context context = InstrumentationRegistry.getTargetContext(); + AttributeCache.init(context); + + sWm = mWmRule.getWindowManagerService(); + beforeCreateDisplay(); + + mWallpaperController = new WallpaperController(sWm); + + context.getDisplay().getDisplayInfo(mDisplayInfo); + mDisplayContent = createNewDisplay(); + sWm.mDisplayEnabled = true; + sWm.mDisplayReady = true; + + // Set-up some common windows. + mCommonWindows = new HashSet(); + synchronized (sWm.mWindowMap) { + mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow"); + mImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "mImeWindow"); + sWm.mInputMethodWindow = mImeWindow; + mImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG, + "mImeDialogWindow"); + mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow"); + mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow"); + mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER, + "mDockedDividerWindow"); + mAppWindow = createCommonWindow(null, TYPE_BASE_APPLICATION, "mAppWindow"); + mChildAppWindowAbove = createCommonWindow(mAppWindow, + TYPE_APPLICATION_ATTACHED_DIALOG, + "mChildAppWindowAbove"); + mChildAppWindowBelow = createCommonWindow(mAppWindow, + TYPE_APPLICATION_MEDIA_OVERLAY, + "mChildAppWindowBelow"); + } + // Adding a display will cause freezing the display. Make sure to wait until it's + // unfrozen to not run into race conditions with the tests. + waitUntilHandlersIdle(); + } catch (Exception e) { + Log.e(TAG, "Failed to set up test", e); + throw e; + } + } + + void beforeCreateDisplay() { + // Called before display is created. + } + + @After + public void tearDown() throws Exception { + // If @After throws an exception, the error isn't logged. This will make sure any failures + // in the tear down are clear. This can be removed when b/37850063 is fixed. + try { + final LinkedList nonCommonWindows = new LinkedList(); + + synchronized (sWm.mWindowMap) { + sWm.mRoot.forAllWindows(w -> { + if (!mCommonWindows.contains(w)) { + nonCommonWindows.addLast(w); + } + }, true /* traverseTopToBottom */); + + while (!nonCommonWindows.isEmpty()) { + nonCommonWindows.pollLast().removeImmediately(); + } + + mDisplayContent.removeImmediately(); + sWm.mInputMethodTarget = null; + sWm.mClosingApps.clear(); + sWm.mOpeningApps.clear(); + } + + // Wait until everything is really cleaned up. + waitUntilHandlersIdle(); + } catch (Exception e) { + Log.e(TAG, "Failed to tear down test", e); + throw e; + } + } + + /** + * @return A SurfaceBuilderFactory to inject in to the WindowManagerService during + * set-up (or null). + */ + SurfaceBuilderFactory getSurfaceBuilderFactory() { + return null; + } + + private WindowState createCommonWindow(WindowState parent, int type, String name) { + synchronized (sWm.mWindowMap) { + final WindowState win = createWindow(parent, type, name); + mCommonWindows.add(win); + // Prevent common windows from been IMe targets + win.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + return win; + } + } + + /** Asserts that the first entry is greater than the second entry. */ + void assertGreaterThan(int first, int second) throws Exception { + Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second); + } + + /** Asserts that the first entry is greater than the second entry. */ + void assertLessThan(int first, int second) throws Exception { + Assert.assertTrue("Excepted " + first + " to be less than " + second, first < second); + } + + /** + * Waits until the main handler for WM has processed all messages. + */ + void waitUntilHandlersIdle() { + mWmRule.waitUntilWindowManagerHandlersIdle(); + } + + private WindowToken createWindowToken( + DisplayContent dc, int windowingMode, int activityType, int type) { + synchronized (sWm.mWindowMap) { + if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) { + return WindowTestUtils.createTestWindowToken(type, dc); + } + + return createAppWindowToken(dc, windowingMode, activityType); + } + } + + AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) { + final TaskStack stack = createStackControllerOnStackOnDisplay(windowingMode, activityType, + dc).mContainer; + final Task task = createTaskInStack(stack, 0 /* userId */); + final WindowTestUtils.TestAppWindowToken appWindowToken = + WindowTestUtils.createTestAppWindowToken(dc); + task.addChild(appWindowToken, 0); + return appWindowToken; + } + + WindowState createWindow(WindowState parent, int type, String name) { + synchronized (sWm.mWindowMap) { + return (parent == null) + ? createWindow(parent, type, mDisplayContent, name) + : createWindow(parent, type, parent.mToken, name); + } + } + + WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType, + int type, DisplayContent dc, String name) { + synchronized (sWm.mWindowMap) { + final WindowToken token = createWindowToken(dc, windowingMode, activityType, type); + return createWindow(parent, type, token, name); + } + } + + WindowState createAppWindow(Task task, int type, String name) { + synchronized (sWm.mWindowMap) { + final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent); + task.addChild(token, 0); + return createWindow(null, type, token, name); + } + } + + WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) { + synchronized (sWm.mWindowMap) { + final WindowToken token = createWindowToken( + dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); + return createWindow(parent, type, token, name); + } + } + + WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name, + boolean ownerCanAddInternalSystemWindow) { + synchronized (sWm.mWindowMap) { + final WindowToken token = createWindowToken( + dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); + return createWindow(parent, type, token, name, 0 /* ownerId */, + ownerCanAddInternalSystemWindow); + } + } + + WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { + synchronized (sWm.mWindowMap) { + return createWindow(parent, type, token, name, 0 /* ownerId */, + false /* ownerCanAddInternalSystemWindow */); + } + } + + WindowState createWindow(WindowState parent, int type, WindowToken token, String name, + int ownerId, boolean ownerCanAddInternalSystemWindow) { + return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow, + sWm, mMockSession, mIWindow); + } + + static WindowState createWindow(WindowState parent, int type, WindowToken token, + String name, int ownerId, boolean ownerCanAddInternalSystemWindow, + WindowManagerService service, Session session, IWindow iWindow) { + synchronized (service.mWindowMap) { + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); + attrs.setTitle(name); + + final WindowState w = new WindowState(service, session, iWindow, token, parent, + OP_NONE, + 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow, + mPowerManagerWrapper); + // TODO: Probably better to make this call in the WindowState ctor to avoid errors with + // adding it to the token... + token.addWindow(w); + return w; + } + } + + /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */ + TaskStack createTaskStackOnDisplay(DisplayContent dc) { + synchronized (sWm.mWindowMap) { + return createStackControllerOnDisplay(dc).mContainer; + } + } + + StackWindowController createStackControllerOnDisplay(DisplayContent dc) { + synchronized (sWm.mWindowMap) { + return createStackControllerOnStackOnDisplay( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc); + } + } + + StackWindowController createStackControllerOnStackOnDisplay( + int windowingMode, int activityType, DisplayContent dc) { + synchronized (sWm.mWindowMap) { + final Configuration overrideConfig = new Configuration(); + overrideConfig.windowConfiguration.setWindowingMode(windowingMode); + overrideConfig.windowConfiguration.setActivityType(activityType); + final int stackId = ++sNextStackId; + final StackWindowController controller = new StackWindowController(stackId, null, + dc.getDisplayId(), true /* onTop */, new Rect(), sWm); + controller.onOverrideConfigurationChanged(overrideConfig); + return controller; + } + } + + /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */ + Task createTaskInStack(TaskStack stack, int userId) { + return WindowTestUtils.createTaskInStack(sWm, stack, userId); + } + + /** Creates a {@link DisplayContent} and adds it to the system. */ + DisplayContent createNewDisplay() { + final int displayId = sNextDisplayId++; + final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, + mDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); + synchronized (sWm.mWindowMap) { + return new DisplayContent(display, sWm, mWallpaperController, + mock(DisplayWindowController.class)); + } + } + + /** Creates a {@link com.android.server.wm.WindowTestUtils.TestWindowState} */ + WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs, + WindowToken token) { + synchronized (sWm.mWindowMap) { + return new WindowTestUtils.TestWindowState(sWm, mMockSession, mIWindow, attrs, token); + } + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java new file mode 100644 index 000000000000..5ea87b14f746 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_TOAST; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the {@link WindowToken} class. + * + * Build/Install/Run: + * atest WmTests:WindowTokenTests + */ +@SmallTest +@FlakyTest(bugId = 74078662) +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowTokenTests extends WindowTestsBase { + + @Test + public void testAddWindow() throws Exception { + final WindowTestUtils.TestWindowToken token = + WindowTestUtils.createTestWindowToken(0, mDisplayContent); + + assertEquals(0, token.getWindowsCount()); + + final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1"); + final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11"); + final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12"); + final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2"); + final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3"); + + token.addWindow(window1); + // NOTE: Child windows will not be added to the token as window containers can only + // contain/reference their direct children. + token.addWindow(window11); + token.addWindow(window12); + token.addWindow(window2); + token.addWindow(window3); + + // Should not contain the child windows that were added above. + assertEquals(3, token.getWindowsCount()); + assertTrue(token.hasWindow(window1)); + assertFalse(token.hasWindow(window11)); + assertFalse(token.hasWindow(window12)); + assertTrue(token.hasWindow(window2)); + assertTrue(token.hasWindow(window3)); + } + + @Test + public void testChildRemoval() throws Exception { + final DisplayContent dc = mDisplayContent; + final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken(0, dc); + + assertEquals(token, dc.getWindowToken(token.token)); + + final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1"); + final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2"); + + window2.removeImmediately(); + // The token should still be mapped in the display content since it still has a child. + assertEquals(token, dc.getWindowToken(token.token)); + + window1.removeImmediately(); + // The token should have been removed from the display content since it no longer has a + // child. + assertEquals(null, dc.getWindowToken(token.token)); + } + + /** + * Test that a window token isn't orphaned by the system when it is requested to be removed. + * Tokens should only be removed from the system when all their windows are gone. + */ + @Test + public void testTokenRemovalProcess() throws Exception { + final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken( + TYPE_TOAST, mDisplayContent, true /* persistOnEmpty */); + + // Verify that the token is on the display + assertNotNull(mDisplayContent.getWindowToken(token.token)); + + final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1"); + final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2"); + + mDisplayContent.removeWindowToken(token.token); + // Verify that the token is no longer mapped on the display + assertNull(mDisplayContent.getWindowToken(token.token)); + // Verify that the token is still attached to its parent + assertNotNull(token.getParent()); + // Verify that the token windows are still around. + assertEquals(2, token.getWindowsCount()); + + window1.removeImmediately(); + // Verify that the token is still attached to its parent + assertNotNull(token.getParent()); + // Verify that the other token window is still around. + assertEquals(1, token.getWindowsCount()); + + window2.removeImmediately(); + // Verify that the token is no-longer attached to its parent + assertNull(token.getParent()); + // Verify that the token windows are no longer attached to it. + assertEquals(0, token.getWindowsCount()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java new file mode 100644 index 000000000000..f60ae75b85f2 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.Preconditions; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +/** + * Test class for {@link WindowTracing}. + * + * Build/Install/Run: + * atest WmTests:WindowTracingTest + */ +@SmallTest +@FlakyTest(bugId = 74078662) +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowTracingTest extends WindowTestsBase { + + private static final byte[] MAGIC_HEADER = new byte[] { + 0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45, + }; + + private Context mTestContext; + private WindowTracing mWindowTracing; + private WindowManagerService mWmMock; + private File mFile; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + mWmMock = mock(WindowManagerService.class); + + mTestContext = InstrumentationRegistry.getContext(); + + mFile = mTestContext.getFileStreamPath("tracing_test.dat"); + mFile.delete(); + + mWindowTracing = new WindowTracing(mFile); + } + + @Test + public void isEnabled_returnsFalseByDefault() throws Exception { + assertFalse(mWindowTracing.isEnabled()); + } + + @Test + public void isEnabled_returnsTrueAfterStart() throws Exception { + mWindowTracing.startTrace(mock(PrintWriter.class)); + assertTrue(mWindowTracing.isEnabled()); + } + + @Test + public void isEnabled_returnsFalseAfterStop() throws Exception { + mWindowTracing.startTrace(mock(PrintWriter.class)); + mWindowTracing.stopTrace(mock(PrintWriter.class)); + assertFalse(mWindowTracing.isEnabled()); + } + + @Test + public void trace_discared_whenNotTracing() throws Exception { + mWindowTracing.traceStateLocked("where", mWmMock); + verifyZeroInteractions(mWmMock); + } + + @Test + public void trace_dumpsWindowManagerState_whenTracing() throws Exception { + mWindowTracing.startTrace(mock(PrintWriter.class)); + mWindowTracing.traceStateLocked("where", mWmMock); + + verify(mWmMock).writeToProtoLocked(any(), eq(true)); + } + + @Test + public void traceFile_startsWithMagicHeader() throws Exception { + mWindowTracing.startTrace(mock(PrintWriter.class)); + mWindowTracing.stopTrace(mock(PrintWriter.class)); + + byte[] header = new byte[MAGIC_HEADER.length]; + try (InputStream is = new FileInputStream(mFile)) { + assertEquals(MAGIC_HEADER.length, is.read(header)); + assertArrayEquals(MAGIC_HEADER, header); + } + } + + @Test + @Ignore("Figure out why this test is crashing when setting up mWmMock.") + public void tracing_endsUpInFile() throws Exception { + mWindowTracing.startTrace(mock(PrintWriter.class)); + + doAnswer((inv) -> { + inv.getArgument(0).write( + WindowManagerTraceProto.WHERE, "TEST_WM_PROTO"); + return null; + }).when(mWmMock).writeToProtoLocked(any(), any()); + mWindowTracing.traceStateLocked("TEST_WHERE", mWmMock); + + mWindowTracing.stopTrace(mock(PrintWriter.class)); + + byte[] file = new byte[1000]; + int fileLength; + try (InputStream is = new FileInputStream(mFile)) { + fileLength = is.read(file); + assertTrue(containsBytes(file, fileLength, + "TEST_WHERE".getBytes(StandardCharsets.UTF_8))); + assertTrue(containsBytes(file, fileLength, + "TEST_WM_PROTO".getBytes(StandardCharsets.UTF_8))); + } + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + + mFile.delete(); + } + + /** Return true if {@code needle} appears anywhere in {@code haystack[0..length]} */ + boolean containsBytes(byte[] haystack, int haystackLenght, byte[] needle) { + Preconditions.checkArgument(haystackLenght > 0); + Preconditions.checkArgument(needle.length > 0); + + outer: for (int i = 0; i <= haystackLenght - needle.length; i++) { + for (int j = 0; j < needle.length; j++) { + if (haystack[i+j] != needle[j]) { + continue outer; + } + } + return true; + } + return false; + } + + @Test + public void test_containsBytes() { + byte[] haystack = "hello_world".getBytes(StandardCharsets.UTF_8); + assertTrue(containsBytes(haystack, haystack.length, + "hello".getBytes(StandardCharsets.UTF_8))); + assertTrue(containsBytes(haystack, haystack.length, + "world".getBytes(StandardCharsets.UTF_8))); + assertFalse(containsBytes(haystack, 6, + "world".getBytes(StandardCharsets.UTF_8))); + assertFalse(containsBytes(haystack, haystack.length, + "world_".getBytes(StandardCharsets.UTF_8))); + assertFalse(containsBytes(haystack, haystack.length, + "absent".getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java new file mode 100644 index 000000000000..a730b5cf84e6 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.LinkedList; + +/** + * Tests for the {@link WindowLayersController} class. + * + * Build/Install/Run: + * atest WmTests:ZOrderingTests + */ +@SmallTest +@FlakyTest(bugId = 74078662) +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ZOrderingTests extends WindowTestsBase { + + private class LayerRecordingTransaction extends SurfaceControl.Transaction { + HashMap mLayersForControl = new HashMap(); + HashMap mRelativeLayersForControl = new HashMap(); + + @Override + public SurfaceControl.Transaction setLayer(SurfaceControl sc, int layer) { + mRelativeLayersForControl.remove(sc); + mLayersForControl.put(sc, layer); + return super.setLayer(sc, layer); + } + + @Override + public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc, + SurfaceControl relativeTo, + int layer) { + mRelativeLayersForControl.put(sc, relativeTo); + mLayersForControl.put(sc, layer); + return super.setRelativeLayer(sc, relativeTo, layer); + } + + private int getLayer(SurfaceControl sc) { + return mLayersForControl.getOrDefault(sc, 0); + } + + private SurfaceControl getRelativeLayer(SurfaceControl sc) { + return mRelativeLayersForControl.get(sc); + } + }; + + // We have WM use our Hierarchy recording subclass of SurfaceControl.Builder + // such that we can keep track of the parents of Surfaces as they are constructed. + private HashMap mParentFor = new HashMap(); + + private class HierarchyRecorder extends SurfaceControl.Builder { + SurfaceControl mPendingParent; + + HierarchyRecorder(SurfaceSession s) { + super(s); + } + + public SurfaceControl.Builder setParent(SurfaceControl sc) { + mPendingParent = sc; + return super.setParent(sc); + } + public SurfaceControl build() { + SurfaceControl sc = super.build(); + mParentFor.put(sc, mPendingParent); + mPendingParent = null; + return sc; + } + }; + + class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory { + public SurfaceControl.Builder make(SurfaceSession s) { + return new HierarchyRecorder(s); + } + }; + + private LayerRecordingTransaction mTransaction; + + @Override + void beforeCreateDisplay() { + // We can't use @Before here because it may happen after WindowTestsBase @Before + // which is after construction of the DisplayContent, meaning the HierarchyRecorder + // would miss construction of the top-level layers. + mTransaction = new LayerRecordingTransaction(); + sWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory(); + sWm.mTransactionFactory = () -> mTransaction; + } + + @After + public void after() { + mTransaction.close(); + mParentFor.clear(); + } + + LinkedList getAncestors(LayerRecordingTransaction t, SurfaceControl sc) { + LinkedList p = new LinkedList(); + SurfaceControl current = sc; + do { + p.addLast(current); + + SurfaceControl rs = t.getRelativeLayer(current); + if (rs != null) { + current = rs; + } else { + current = mParentFor.get(current); + } + } while (current != null); + return p; + } + + + void assertZOrderGreaterThan(LayerRecordingTransaction t, SurfaceControl left, + SurfaceControl right) throws Exception { + final LinkedList leftParentChain = getAncestors(t, left); + final LinkedList rightParentChain = getAncestors(t, right); + + SurfaceControl commonAncestor = null; + SurfaceControl leftTop = leftParentChain.peekLast(); + SurfaceControl rightTop = rightParentChain.peekLast(); + while (leftTop != null && rightTop != null && leftTop == rightTop) { + commonAncestor = leftParentChain.removeLast(); + rightParentChain.removeLast(); + leftTop = leftParentChain.peekLast(); + rightTop = rightParentChain.peekLast(); + } + + if (rightTop == null) { // right is the parent of left. + assertGreaterThan(t.getLayer(leftTop), 0); + } else if (leftTop == null) { // left is the parent of right. + assertGreaterThan(0, t.getLayer(rightTop)); + } else { + assertGreaterThan(t.getLayer(leftTop), + t.getLayer(rightTop)); + } + } + + void assertWindowHigher(WindowState left, WindowState right) throws Exception { + assertZOrderGreaterThan(mTransaction, left.getSurfaceControl(), right.getSurfaceControl()); + } + + WindowState createWindow(String name) { + return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name); + } + + @Test + public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception { + sWm.mInputMethodTarget = null; + mDisplayContent.assignChildLayers(mTransaction); + + // The Ime has an higher base layer than app windows and lower base layer than system + // windows, so it should be above app windows and below system windows if there isn't an IME + // target. + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); + + // And, IME dialogs should always have an higher layer than the IME. + assertWindowHigher(mImeDialogWindow, mImeWindow); + } + + @Test + public void testAssignWindowLayers_ForImeWithAppTarget() throws Exception { + final WindowState imeAppTarget = createWindow("imeAppTarget"); + sWm.mInputMethodTarget = imeAppTarget; + + mDisplayContent.assignChildLayers(mTransaction); + + // Ime should be above all app windows and below system windows if it is targeting an app + // window. + assertWindowHigher(mImeWindow, imeAppTarget); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); + + // And, IME dialogs should always have an higher layer than the IME. + assertWindowHigher(mImeDialogWindow, mImeWindow); + } + + @Test + public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception { + final WindowState imeAppTarget = createWindow("imeAppTarget"); + final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget, + TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken, + "imeAppTargetChildAboveWindow"); + final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget, + TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken, + "imeAppTargetChildBelowWindow"); + + sWm.mInputMethodTarget = imeAppTarget; + mDisplayContent.assignChildLayers(mTransaction); + + // Ime should be above all app windows except for child windows that are z-ordered above it + // and below system windows if it is targeting an app window. + assertWindowHigher(mImeWindow, imeAppTarget); + assertWindowHigher(imeAppTargetChildAboveWindow, mImeWindow); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); + + // And, IME dialogs should always have an higher layer than the IME. + assertWindowHigher(mImeDialogWindow, mImeWindow); + } + + @Test + public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception { + final WindowState appBelowImeTarget = createWindow("appBelowImeTarget"); + final WindowState imeAppTarget = createWindow("imeAppTarget"); + final WindowState appAboveImeTarget = createWindow("appAboveImeTarget"); + + sWm.mInputMethodTarget = imeAppTarget; + mDisplayContent.assignChildLayers(mTransaction); + + // Ime should be above all app windows except for non-fullscreen app window above it and + // below system windows if it is targeting an app window. + assertWindowHigher(mImeWindow, imeAppTarget); + assertWindowHigher(mImeWindow, appBelowImeTarget); + assertWindowHigher(appAboveImeTarget, mImeWindow); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); + + // And, IME dialogs should always have an higher layer than the IME. + assertWindowHigher(mImeDialogWindow, mImeWindow); + } + + @Test + public void testAssignWindowLayers_ForImeNonAppImeTarget() throws Exception { + final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY, + mDisplayContent, "imeSystemOverlayTarget", + true /* ownerCanAddInternalSystemWindow */); + + sWm.mInputMethodTarget = imeSystemOverlayTarget; + mDisplayContent.assignChildLayers(mTransaction); + + // The IME target base layer is higher than all window except for the nav bar window, so the + // IME should be above all windows except for the nav bar. + assertWindowHigher(mImeWindow, imeSystemOverlayTarget); + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mImeWindow, mDockedDividerWindow); + + // The IME has a higher base layer than the status bar so we may expect it to go + // above the status bar once they are both in the Non-App layer, as past versions of this + // test enforced. However this seems like the wrong behavior unless the status bar is the + // IME target. + assertWindowHigher(mNavBarWindow, mImeWindow); + assertWindowHigher(mStatusBarWindow, mImeWindow); + + // And, IME dialogs should always have an higher layer than the IME. + assertWindowHigher(mImeDialogWindow, mImeWindow); + } + + @Test + public void testAssignWindowLayers_ForStatusBarImeTarget() throws Exception { + sWm.mInputMethodTarget = mStatusBarWindow; + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(mImeWindow, mChildAppWindowAbove); + assertWindowHigher(mImeWindow, mAppWindow); + assertWindowHigher(mImeWindow, mDockedDividerWindow); + assertWindowHigher(mImeWindow, mStatusBarWindow); + + // And, IME dialogs should always have an higher layer than the IME. + assertWindowHigher(mImeDialogWindow, mImeWindow); + } + + @Test + public void testStackLayers() throws Exception { + final WindowState anyWindow1 = createWindow("anyWindow"); + final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED, + ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, + "pinnedStackWindow"); + final WindowState dockedStackWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + mDisplayContent, "dockedStackWindow"); + final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, + mDisplayContent, "assistantStackWindow"); + final WindowState homeActivityWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_HOME, TYPE_BASE_APPLICATION, + mDisplayContent, "homeActivityWindow"); + final WindowState anyWindow2 = createWindow("anyWindow2"); + + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(dockedStackWindow, homeActivityWindow); + assertWindowHigher(assistantStackWindow, homeActivityWindow); + assertWindowHigher(pinnedStackWindow, homeActivityWindow); + assertWindowHigher(anyWindow1, homeActivityWindow); + assertWindowHigher(anyWindow2, homeActivityWindow); + assertWindowHigher(pinnedStackWindow, anyWindow1); + assertWindowHigher(pinnedStackWindow, anyWindow2); + assertWindowHigher(pinnedStackWindow, dockedStackWindow); + assertWindowHigher(pinnedStackWindow, assistantStackWindow); + } + + @Test + public void testAssignWindowLayers_ForSysUiPanels() throws Exception { + final WindowState navBarPanel = + createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel"); + final WindowState statusBarPanel = + createWindow(null, TYPE_STATUS_BAR_PANEL, mDisplayContent, "StatusBarPanel"); + final WindowState statusBarSubPanel = + createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel"); + mDisplayContent.assignChildLayers(mTransaction); + + // Ime should be above all app windows and below system windows if it is targeting an app + // window. + assertWindowHigher(navBarPanel, mNavBarWindow); + assertWindowHigher(statusBarPanel, mStatusBarWindow); + assertWindowHigher(statusBarSubPanel, statusBarPanel); + } + + @Test + public void testAssignWindowLayers_ForNegativelyZOrderedSubtype() throws Exception { + // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA + // then we can drop all negative layering on the windowing side. + + final WindowState anyWindow = createWindow("anyWindow"); + final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent, + "TypeApplicationMediaChild"); + final WindowState mediaOverlayChild = createWindow(anyWindow, TYPE_APPLICATION_MEDIA_OVERLAY, + mDisplayContent, "TypeApplicationMediaOverlayChild"); + + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(anyWindow, mediaOverlayChild); + assertWindowHigher(mediaOverlayChild, child); + } + + @Test + public void testDockedDividerPosition() throws Exception { + final WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED, + ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, + "pinnedStackWindow"); + final WindowState splitScreenWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + mDisplayContent, "splitScreenWindow"); + final WindowState splitScreenSecondaryWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); + final WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, + mDisplayContent, "assistantStackWindow"); + + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(mDockedDividerWindow, splitScreenWindow); + assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow); + assertWindowHigher(pinnedStackWindow, mDockedDividerWindow); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java new file mode 100644 index 000000000000..f0f3023f1f17 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static com.android.server.wm.utils.CoordinateTransforms + .transformPhysicalToLogicalCoordinates; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.PointF; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +/** + * Tests for {@link CoordinateTransforms}. + * + * Build/Install/Run: + * atest WmTests:CoordinateTransformsTest + */ +public class CoordinateTransformsTest { + + private static final int W = 200; + private static final int H = 400; + + private final Matrix mMatrix = new Matrix(); + + @Rule + public final ErrorCollector mErrorCollector = new ErrorCollector(); + + @Before + public void setUp() throws Exception { + mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot0() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix); + assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX)); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot90() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(0, W); + checkDevicePoint(W, H).mapsToLogicalPoint(H, 0); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot180() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(W, H); + checkDevicePoint(W, H).mapsToLogicalPoint(0, 0); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot270() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0); + checkDevicePoint(W, H).mapsToLogicalPoint(0, W); + } + + private DevicePointAssertable checkDevicePoint(int x, int y) { + final Point devicePoint = new Point(x, y); + final float[] fs = new float[] {x, y}; + mMatrix.mapPoints(fs); + final PointF transformedPoint = new PointF(fs[0], fs[1]); + + return (expectedX, expectedY) -> { + mErrorCollector.checkThat("t(" + devicePoint + ")", + transformedPoint, is(new PointF(expectedX, expectedY))); + }; + } + + public interface DevicePointAssertable { + void mapsToLogicalPoint(int x, int y); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/InsetUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/InsetUtilsTest.java new file mode 100644 index 000000000000..ff3d883056e4 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/InsetUtilsTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm.utils; + +import static junit.framework.Assert.assertEquals; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link InsetUtils}. + * + * Build/Install/Run: + * atest WmTests:InsetUtilsTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class InsetUtilsTest { + + @Test + public void testAdd() throws Exception { + final Rect rect1 = new Rect(10, 20, 30, 40); + final Rect rect2 = new Rect(50, 60, 70, 80); + InsetUtils.addInsets(rect1, rect2); + assertEquals(new Rect(60, 80, 100, 120), rect1); + } +} + diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationCacheTest.java new file mode 100644 index 000000000000..c800fa2fe6b1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/RotationCacheTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import static android.util.Pair.create; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Pair; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link RotationCache}. + * + * Build/Install/Run: + * atest WmTests:RotationCacheTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@FlakyTest(bugId = 74078662) +@Presubmit +public class RotationCacheTest { + + private RotationCache> mCache; + private boolean mComputationCalled; + + @Before + public void setUp() throws Exception { + mComputationCalled = false; + mCache = new RotationCache<>((o, rot) -> { + mComputationCalled = true; + return create(o, rot); + }); + } + + @Test + public void getOrCompute_computes() throws Exception { + assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0))); + assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); + assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2))); + assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3))); + } + + @Test + public void getOrCompute_sameParam_sameRot_hitsCache() throws Exception { + assertNotNull(mCache.getOrCompute("hello", 1)); + + mComputationCalled = false; + assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); + assertThat(mComputationCalled, is(false)); + } + + @Test + public void getOrCompute_sameParam_hitsCache_forAllRots() throws Exception { + assertNotNull(mCache.getOrCompute("hello", 3)); + assertNotNull(mCache.getOrCompute("hello", 2)); + assertNotNull(mCache.getOrCompute("hello", 1)); + assertNotNull(mCache.getOrCompute("hello", 0)); + + mComputationCalled = false; + assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); + assertThat(mCache.getOrCompute("hello", 0), equalTo(create("hello", 0))); + assertThat(mCache.getOrCompute("hello", 2), equalTo(create("hello", 2))); + assertThat(mCache.getOrCompute("hello", 3), equalTo(create("hello", 3))); + assertThat(mComputationCalled, is(false)); + } + + @Test + public void getOrCompute_changingParam_recomputes() throws Exception { + assertNotNull(mCache.getOrCompute("hello", 1)); + + assertThat(mCache.getOrCompute("world", 1), equalTo(create("world", 1))); + } + + @Test + public void getOrCompute_changingParam_clearsCacheForDifferentRots() throws Exception { + assertNotNull(mCache.getOrCompute("hello", 1)); + assertNotNull(mCache.getOrCompute("world", 2)); + + mComputationCalled = false; + assertThat(mCache.getOrCompute("hello", 1), equalTo(create("hello", 1))); + assertThat(mComputationCalled, is(true)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java new file mode 100644 index 000000000000..f4c208b4ce0e --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import static android.view.DisplayCutout.NO_CUTOUT; +import static android.view.DisplayCutout.fromBoundingRect; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Size; +import android.view.DisplayCutout; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +/** + * Tests for {@link WmDisplayCutout} + * + * Build/Install/Run: + * atest WmTests:WmDisplayCutoutTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WmDisplayCutoutTest { + + private final DisplayCutout mCutoutTop = new DisplayCutout( + new Rect(0, 100, 0, 0), + Arrays.asList(new Rect(50, 0, 75, 100))); + + @Test + public void calculateRelativeTo_top() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 100, 20), 200, 400) + .calculateRelativeTo(new Rect(5, 5, 95, 195)); + + assertEquals(new Rect(0, 15, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_left() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 20, 100), 400, 200) + .calculateRelativeTo(new Rect(5, 5, 195, 95)); + + assertEquals(new Rect(15, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_bottom() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 180, 100, 200), 100, 200) + .calculateRelativeTo(new Rect(5, 5, 95, 195)); + + assertEquals(new Rect(0, 0, 0, 15), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_right() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(180, 0, 200, 100), 200, 100) + .calculateRelativeTo(new Rect(5, 5, 195, 95)); + + assertEquals(new Rect(0, 0, 15, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void calculateRelativeTo_bounds() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 100, 20), 200, 400) + .calculateRelativeTo(new Rect(5, 10, 95, 180)); + + assertEquals(new Rect(-5, -10, 95, 10), cutout.getDisplayCutout().getBounds().getBounds()); + } + + @Test + public void computeSafeInsets_top() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 100, 20), 200, 400); + + assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_left() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 0, 20, 100), 400, 200); + + assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_bottom() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(0, 180, 100, 200), 100, 200); + + assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_right() { + WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets( + fromBoundingRect(180, 0, 200, 100), 200, 100); + + assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets()); + } + + @Test + public void computeSafeInsets_bounds() { + DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000, + 2000).getDisplayCutout(); + + assertEquals(mCutoutTop.getBounds().getBounds(), + cutout.getBounds().getBounds()); + } + + @Test + public void test_equals() { + assertEquals(new WmDisplayCutout(NO_CUTOUT, null), new WmDisplayCutout(NO_CUTOUT, null)); + assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), + new WmDisplayCutout(mCutoutTop, new Size(1, 2))); + + assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), + new WmDisplayCutout(mCutoutTop, new Size(5, 6))); + assertNotEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)), + new WmDisplayCutout(NO_CUTOUT, new Size(1, 2))); + } + + @Test + public void test_hashCode() { + assertEquals(new WmDisplayCutout(NO_CUTOUT, null).hashCode(), + new WmDisplayCutout(NO_CUTOUT, null).hashCode()); + assertEquals(new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode(), + new WmDisplayCutout(mCutoutTop, new Size(1, 2)).hashCode()); + } +} \ No newline at end of file -- cgit v1.2.3-59-g8ed1b