summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/AppJankTest/Android.bp37
-rw-r--r--tests/AppJankTest/AndroidManifest.xml40
-rw-r--r--tests/AppJankTest/AndroidTest.xml38
-rw-r--r--tests/AppJankTest/OWNERS4
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java22
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java279
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java156
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java227
-rw-r--r--tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt17
-rw-r--r--tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt39
-rw-r--r--tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt78
-rw-r--r--tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java13
-rw-r--r--tests/BootImageProfileTest/Android.bp1
-rw-r--r--tests/FlickerTests/ActivityEmbedding/Android.bp141
-rw-r--r--tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt4
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt10
-rw-r--r--tests/FlickerTests/Android.bp1
-rw-r--r--tests/FlickerTests/AppClose/Android.bp31
-rw-r--r--tests/FlickerTests/AppClose/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt2
-rw-r--r--tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/Android.bp141
-rw-r--r--tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/AppLaunch/OWNERS2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt16
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt2
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt1
-rw-r--r--tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt2
-rw-r--r--tests/FlickerTests/FlickerService/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt3
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt3
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt3
-rw-r--r--tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt3
-rw-r--r--tests/FlickerTests/IME/Android.bp173
-rw-r--r--tests/FlickerTests/IME/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt6
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt4
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt2
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt22
-rw-r--r--tests/FlickerTests/Notification/Android.bp54
-rw-r--r--tests/FlickerTests/Notification/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt10
-rw-r--r--tests/FlickerTests/QuickSwitch/Android.bp38
-rw-r--r--tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt2
-rw-r--r--tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt2
-rw-r--r--tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt2
-rw-r--r--tests/FlickerTests/Rotation/Android.bp38
-rw-r--r--tests/FlickerTests/Rotation/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt2
-rw-r--r--tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt20
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/Android.bp1
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt19
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt309
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt6
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt160
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt16
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt126
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/Android.bp2
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml45
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml2
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml14
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml32
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java12
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java4
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java28
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java79
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java156
-rw-r--r--tests/Input/Android.bp5
-rw-r--r--tests/Input/AndroidManifest.xml8
-rw-r--r--tests/Input/AndroidTest.xml8
-rw-r--r--tests/Input/assets/testPointerFillStyle.pngbin569 -> 578 bytes
-rw-r--r--tests/Input/assets/testPointerStrokeStyle.pngbin0 -> 378 bytes
-rw-r--r--tests/Input/res/drawable/test_key_drawable.xml21
-rw-r--r--tests/Input/res/drawable/test_modifier_drawable.xml21
-rw-r--r--tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu150
-rw-r--r--tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json34
-rw-r--r--tests/Input/res/xml/bookmarks.xml60
-rw-r--r--tests/Input/res/xml/keyboard_glyph_maps.xml26
-rw-r--r--tests/Input/res/xml/test_glyph_map.xml33
-rw-r--r--tests/Input/res/xml/test_glyph_map2.xml33
-rw-r--r--tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt20
-rw-r--r--tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java34
-rw-r--r--tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java50
-rw-r--r--tests/Input/src/android/hardware/input/InputManagerTest.kt26
-rw-r--r--tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt222
-rw-r--r--tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt194
-rw-r--r--tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt33
-rw-r--r--tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt31
-rw-r--r--tests/Input/src/com/android/server/input/BatteryControllerTests.kt43
-rw-r--r--tests/Input/src/com/android/server/input/InputGestureManagerTests.kt156
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt214
-rw-r--r--tests/Input/src/com/android/server/input/InputShellCommandTest.java15
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt1466
-rw-r--r--tests/Input/src/com/android/server/input/KeyRemapperTests.kt32
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt118
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt193
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt37
-rw-r--r--tests/Input/src/com/android/server/input/PointerIconCacheTest.kt135
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java219
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java474
-rw-r--r--tests/Input/src/com/android/test/input/AnrTest.kt10
-rw-r--r--tests/Input/src/com/android/test/input/CaptureEventActivity.kt85
-rw-r--r--tests/Input/src/com/android/test/input/InputEventAssignerTest.kt186
-rw-r--r--tests/Input/src/com/android/test/input/MockInputManagerRule.kt42
-rw-r--r--tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt46
-rw-r--r--tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt215
-rw-r--r--tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt4
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java8
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java2
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java2
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java2
-rw-r--r--tests/Internal/Android.bp29
-rw-r--r--tests/Internal/AndroidTest.xml8
-rw-r--r--tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml29
-rw-r--r--tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml37
-rw-r--r--tests/Internal/ApplicationSharedMemoryTest32/OWNERS1
-rw-r--r--tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java5
-rw-r--r--tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java124
-rw-r--r--tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java56
-rw-r--r--tests/Internal/src/com/android/internal/util/ParcellingTests.java5
-rw-r--r--tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java18
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java34
-rw-r--r--tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java67
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java1
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java1
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java1
-rw-r--r--tests/TouchLatency/app/src/main/res/values/styles.xml2
-rw-r--r--tests/Tracing/Android.bp2
-rw-r--r--tests/Tracing/TEST_MAPPING2
-rw-r--r--tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java709
-rw-r--r--tests/Tracing/src/android/tracing/perfetto/TestDataSource.java122
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java26
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java (renamed from tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java)601
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java213
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java295
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java25
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java138
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java162
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java5
-rw-r--r--tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java31
-rw-r--r--tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java21
-rw-r--r--tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java20
-rw-r--r--tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java17
-rw-r--r--tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java17
-rw-r--r--tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java20
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java146
-rw-r--r--tests/broadcasts/OWNERS2
-rw-r--r--tests/broadcasts/unit/Android.bp45
-rw-r--r--tests/broadcasts/unit/AndroidManifest.xml27
-rw-r--r--tests/broadcasts/unit/AndroidTest.xml29
-rw-r--r--tests/broadcasts/unit/TEST_MAPPING15
-rw-r--r--tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java258
-rw-r--r--tests/graphics/HwAccelerationTest/AndroidManifest.xml20
-rw-r--r--tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml131
-rw-r--r--tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt58
-rw-r--r--tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt57
-rw-r--r--tests/graphics/SilkFX/res/layout/view_blur_behind.xml148
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt3
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt30
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp1
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java267
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java93
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java10
-rw-r--r--tests/testables/Android.bp9
-rw-r--r--tests/testables/src/android/animation/AnimatorTestRule.java378
-rw-r--r--tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt215
-rw-r--r--tests/testables/src/android/testing/TestWithLooperRule.java8
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java18
-rw-r--r--tests/testables/tests/Android.bp11
-rw-r--r--tests/testables/tests/AndroidManifest.xml4
-rw-r--r--tests/testables/tests/AndroidTest.xml51
-rw-r--r--tests/testables/tests/goldens/recordFilmstrip_withAnimator.pngbin0 -> 40500 bytes
-rw-r--r--tests/testables/tests/goldens/recordFilmstrip_withSpring.pngbin0 -> 32206 bytes
-rw-r--r--tests/testables/tests/goldens/recordTimeSeries_withAnimator.json64
-rw-r--r--tests/testables/tests/goldens/recordTimeSeries_withSpring.json48
-rw-r--r--tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt89
-rw-r--r--tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt196
-rw-r--r--tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt201
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java42
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java16
212 files changed, 12033 insertions, 1010 deletions
diff --git a/tests/AppJankTest/Android.bp b/tests/AppJankTest/Android.bp
new file mode 100644
index 000000000000..acf8dc9aca47
--- /dev/null
+++ b/tests/AppJankTest/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "CoreAppJankTestCases",
+ team: "trendy_team_system_performance",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.core",
+ "platform-test-annotations",
+ "flag-junit",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ certificate: "platform",
+}
diff --git a/tests/AppJankTest/AndroidManifest.xml b/tests/AppJankTest/AndroidManifest.xml
new file mode 100644
index 000000000000..ae973393b90e
--- /dev/null
+++ b/tests/AppJankTest/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.jank.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".EmptyActivity"
+ android:label="EmptyActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.app.jank.tests"
+ android:label="Core tests of App Jank Tracking">
+ </instrumentation>
+
+</manifest> \ No newline at end of file
diff --git a/tests/AppJankTest/AndroidTest.xml b/tests/AppJankTest/AndroidTest.xml
new file mode 100644
index 000000000000..c01c75c9695c
--- /dev/null
+++ b/tests/AppJankTest/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+<configuration description="Config for Core App Jank Tests">
+ <option name="test-suite-tag" value="apct"/>
+
+ <option name="config-descriptor:metadata" key="component" value="systems"/>
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+ <option name="not-shardable" value="true" />
+ <option name="install-arg" value="-t" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="CoreAppJankTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.app.jank.tests"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration> \ No newline at end of file
diff --git a/tests/AppJankTest/OWNERS b/tests/AppJankTest/OWNERS
new file mode 100644
index 000000000000..806de574b071
--- /dev/null
+++ b/tests/AppJankTest/OWNERS
@@ -0,0 +1,4 @@
+steventerrell@google.com
+carmenjackson@google.com
+jjaggi@google.com
+pmuetschard@google.com \ No newline at end of file
diff --git a/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java b/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java
new file mode 100644
index 000000000000..b326765ab097
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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 android.app.jank.tests;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
new file mode 100644
index 000000000000..2cd625eec032
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2024 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 android.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankDataProcessor;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class JankDataProcessorTest {
+
+ private Choreographer mChoreographer;
+ private StateTracker mStateTracker;
+ private JankDataProcessor mJankDataProcessor;
+ private static final int NANOS_PER_MS = 1_000_000;
+ private static String sActivityName;
+ private static ActivityScenario<EmptyActivity> sEmptyActivityActivityScenario;
+ private static final int APP_ID = 25;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityActivityScenario = ActivityScenario.launch(EmptyActivity.class);
+ sActivityName = sEmptyActivityActivityScenario.toString();
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityActivityScenario.close();
+ }
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mStateTracker = new StateTracker(mChoreographer);
+ mJankDataProcessor = new JankDataProcessor(mStateTracker);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_multipleFramesAndStates_attributesTotalFramesCorrectly() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ long totalFramesAttributed = getTotalFramesCounted();
+
+ // Each state is active for each frame that is passed in, there are two states being tested
+ // which is why jankData.size is multiplied by 2.
+ assertEquals(jankData.size() * 2, totalFramesAttributed);
+ }
+
+ /**
+ * Each JankData frame has an associated vsyncid, only frames that have vsyncids between the
+ * StatData start and end vsyncids should be counted. This test confirms that if JankData
+ * does not share any frames with the states then no jank stats are added.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_outOfRangeVsyncId_skipOutOfRangeVsyncIds() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_outOfRange());
+
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ assertEquals(0, mJankDataProcessor.getPendingJankStats().size());
+ }
+
+ /**
+ * It's expected to see many duplicate widget states, if a user is scrolling then
+ * pauses and resumes scrolling again, we may get three widget states two of which are the same.
+ * State 1: {Scroll,WidgetId,Scrolling} State 2: {Scroll,WidgetId,None}
+ * State 3: {Scroll,WidgetId,Scrolling}
+ * These duplicate states should coalesce into only one Jank stat. This test confirms that
+ * behavior.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_duplicateStates_confirmDuplicatesCoalesce() {
+ // getMockStateData will return 10 states 5 of which are set to none and 5 of which are
+ // scrolling.
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+ mJankDataProcessor.processJankData(getMockJankData_vsyncId_inRange(), sActivityName,
+ APP_ID);
+
+ // Confirm the duplicate states are coalesced down to 2 stats 1 for the scrolling state
+ // another for the none state.
+ assertEquals(2, mJankDataProcessor.getPendingJankStats().size());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_inRangeVsyncIds_confirmOnlyInRangeFramesCounted() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ int inRangeFrameCount = jankData.size();
+
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ // Two states are active for each frame which is why inRangeFrameCount is multiplied by 2.
+ assertEquals(inRangeFrameCount * 2, getTotalFramesCounted());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void processJankData_inRangeVsyncIds_confirmHistogramCountMatchesFrameCount() {
+ List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+ mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+ mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+ long totalFrames = getTotalFramesCounted();
+ long histogramFrames = getHistogramFrameCount();
+
+ assertEquals(totalFrames, histogramFrames);
+ }
+
+ // TODO b/375005277 add tests that cover logging and releasing resources back to pool.
+
+ private long getTotalFramesCounted() {
+ return mJankDataProcessor.getPendingJankStats().values()
+ .stream().mapToLong(stat -> stat.getTotalFrames()).sum();
+ }
+
+ private long getHistogramFrameCount() {
+ long totalHistogramFrames = 0;
+
+ for (JankDataProcessor.PendingJankStat stats :
+ mJankDataProcessor.getPendingJankStats().values()) {
+ int[] overrunHistogram = stats.getFrameOverrunBuckets();
+
+ for (int i = 0; i < overrunHistogram.length; i++) {
+ totalHistogramFrames += overrunHistogram[i];
+ }
+ }
+
+ return totalHistogramFrames;
+ }
+
+ /**
+ * Out of range data will have a mVsyncIdStart and mVsyncIdEnd values set to below 25.
+ */
+ private List<StateTracker.StateData> getMockStateData_vsyncId_outOfRange() {
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+ StateTracker.StateData newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = 20;
+ newStateData.mStateDataKey = "Test1_OutBand";
+ newStateData.mVsyncIdStart = 1;
+ newStateData.mWidgetState = "scrolling";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+ stateData.add(newStateData);
+
+ newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = 24;
+ newStateData.mStateDataKey = "Test1_InBand";
+ newStateData.mVsyncIdStart = 20;
+ newStateData.mWidgetState = "Idle";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+ stateData.add(newStateData);
+
+ newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = 20;
+ newStateData.mStateDataKey = "Test1_OutBand";
+ newStateData.mVsyncIdStart = 12;
+ newStateData.mWidgetState = "Idle";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+ stateData.add(newStateData);
+
+ return stateData;
+ }
+
+ /**
+ * This method returns two unique states, one state is set to scrolling the other is set
+ * to none. Both states will have the same startvsyncid to ensure each state is counted the same
+ * number of times. This keeps logic in asserts easier to reason about. Both states will have
+ * a startVsyncId between 25 and 35.
+ */
+ private List<StateTracker.StateData> getMockStateData_vsyncId_inRange() {
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+
+ for (int i = 0; i < 10; i++) {
+ StateTracker.StateData newStateData = new StateTracker.StateData();
+ newStateData.mVsyncIdEnd = Long.MAX_VALUE;
+ newStateData.mStateDataKey = "Test1_" + (i % 2 == 0 ? "scrolling" : "none");
+ // Divide i by two to ensure both the scrolling and none states get the same vsyncid
+ // This makes asserts in tests easier to reason about as each state should be counted
+ // the same number of times.
+ newStateData.mVsyncIdStart = 25 + (i / 2);
+ newStateData.mWidgetState = i % 2 == 0 ? "scrolling" : "none";
+ newStateData.mWidgetId = "widgetId";
+ newStateData.mWidgetCategory = "Scroll";
+
+ stateData.add(newStateData);
+ }
+
+ return stateData;
+ }
+
+ /**
+ * In range data will have a frameVsyncId value between 25 and 35.
+ */
+ private List<SurfaceControl.JankData> getMockJankData_vsyncId_inRange() {
+ ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ mockData.add(new SurfaceControl.JankData(
+ /*frameVsyncId*/25 + i,
+ SurfaceControl.JankData.JANK_NONE,
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i)));
+
+ }
+
+ return mockData;
+ }
+
+ /**
+ * Out of range data will have frameVsyncId values below 25.
+ */
+ private List<SurfaceControl.JankData> getMockJankData_vsyncId_outOfRange() {
+ ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+ for (int i = 0; i < 10; i++) {
+ mockData.add(new SurfaceControl.JankData(
+ /*frameVsyncId*/i,
+ SurfaceControl.JankData.JANK_NONE,
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i),
+ NANOS_PER_MS * ((long) i)));
+
+ }
+
+ return mockData;
+ }
+
+}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
new file mode 100644
index 000000000000..a3e5533599bc
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 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 android.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankTracker;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class JankTrackerTest {
+ private Choreographer mChoreographer;
+ private JankTracker mJankTracker;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Start an empty activity so decore view is not null when creating the JankTracker instance.
+ */
+ private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+ private static String sActivityName;
+
+ private static View sActivityDecorView;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+ sEmptyActivityRule.onActivity(activity -> {
+ sActivityDecorView = activity.getWindow().getDecorView();
+ sActivityName = activity.toString();
+ });
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityRule.close();
+ }
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mJankTracker = new JankTracker(mChoreographer, sActivityDecorView);
+ mJankTracker.setActivityName(sActivityName);
+ }
+
+ /**
+ * When jank tracking is enabled the activity name should be added as a state to associate
+ * frames to it.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTracking_WhenEnabled_ActivityAdded() {
+ mJankTracker.enableAppJankTracking();
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+
+ StateTracker.StateData firstState = stateData.getFirst();
+
+ assertEquals(sActivityName, firstState.mWidgetId);
+ }
+
+ /**
+ * No states should be added when tracking is disabled.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() {
+ mJankTracker.disableAppJankTracking();
+
+ mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+ "FAKE_STATE");
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(0, stateData.size());
+ }
+
+ /**
+ * The activity name as well as the test state should be added for frame association.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingEnabled_StatesShould_BeAddedToTracker() {
+ mJankTracker.forceListenerRegistration();
+
+ mJankTracker.enableAppJankTracking();
+ mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+ "FAKE_STATE");
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(2, stateData.size());
+ }
+
+ /**
+ * Activity state should only be added once even if jank tracking is enabled multiple times.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() {
+ mJankTracker.enableAppJankTracking();
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+
+ stateData.clear();
+
+ mJankTracker.enableAppJankTracking();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+ }
+}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java
new file mode 100644
index 000000000000..541009e05e55
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 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 android.app.jank.tests;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.jank.Flags;
+import android.app.jank.StateTracker;
+import android.app.jank.StateTracker.StateData;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+
+@RunWith(AndroidJUnit4.class)
+public class StateTrackerTest {
+
+ private static final String WIDGET_CATEGORY_NONE = "None";
+ private static final String WIDGET_CATEGORY_SCROLL = "Scroll";
+ private static final String WIDGET_STATE_IDLE = "Idle";
+ private static final String WIDGET_STATE_SCROLLING = "Scrolling";
+ private StateTracker mStateTracker;
+ private Choreographer mChoreographer;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Start an empty activity so choreographer won't return -1 for vsyncid.
+ */
+ private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityRule.close();
+ }
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mStateTracker = new StateTracker(mChoreographer);
+ }
+
+ /**
+ * Check that the start vsyncid is added when the state is first added and end vsyncid is
+ * set to the default value, indicating it has not been updated.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void addWidgetState_VerifyStateHasStartVsyncId() {
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addWidgetState_VerifyStateHasStartVsyncId");
+
+ ArrayList<StateData> stateList = new ArrayList<StateData>();
+ mStateTracker.retrieveAllStates(stateList);
+ StateData stateData = stateList.get(0);
+
+ assertTrue(stateData.mVsyncIdStart > 0);
+ assertTrue(stateData.mVsyncIdEnd == Long.MAX_VALUE);
+ }
+
+ /**
+ * Check that the end vsyncid is added when the state is removed.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void removeWidgetState_VerifyStateHasEndVsyncId() {
+
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "removeWidgetState_VerifyStateHasEndVsyncId");
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "removeWidgetState_VerifyStateHasEndVsyncId");
+
+ ArrayList<StateData> stateList = new ArrayList<StateData>();
+ mStateTracker.retrieveAllStates(stateList);
+ StateData stateData = stateList.get(0);
+
+ assertTrue(stateData.mVsyncIdStart > 0);
+ assertTrue(stateData.mVsyncIdEnd != Long.MAX_VALUE);
+ }
+
+ /**
+ * Check that duplicate states are aggregated into only one active instance.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void addDuplicateStates_ConfirmStateCountOnlyOne() {
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addDuplicateStates_ConfirmStateCountOnlyOne");
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(stateList.size(), 1);
+
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addDuplicateStates_ConfirmStateCountOnlyOne");
+
+ stateList.clear();
+
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(stateList.size(), 1);
+ }
+
+ /**
+ * Check that correct distinct states are returned when all states are retrieved.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void addThreeStateChanges_ConfirmThreeStatesReturned() {
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addThreeStateChanges_ConfirmThreeStatesReturned");
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addThreeStateChanges_ConfirmThreeStatesReturned_01");
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ "addThreeStateChanges_ConfirmThreeStatesReturned_02");
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(stateList.size(), 3);
+ }
+
+ /**
+ * Confirm when states are added and removed the removed states are moved to the previousStates
+ * list and returned when retrieveAllStates is called.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void simulateAddingSeveralStates() {
+ for (int i = 0; i < 20; i++) {
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i - 1));
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i));
+ }
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ int countStatesWithEndVsync = 0;
+ for (int i = 0; i < stateList.size(); i++) {
+ if (stateList.get(i).mVsyncIdEnd != Long.MAX_VALUE) {
+ countStatesWithEndVsync++;
+ }
+ }
+
+ // The last state that was added would be an active state and should not have an associated
+ // end vsyncid.
+ assertEquals(19, countStatesWithEndVsync);
+ }
+
+ /**
+ * Confirm once a state has been attributed to a frame it has been removed from the previous
+ * state list.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void confirmProcessedStates_RemovedFromPreviousStateList() {
+ for (int i = 0; i < 20; i++) {
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i - 1));
+ mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i));
+
+ if (i == 19) {
+ mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING,
+ String.format("simulateAddingSeveralStates_%s", i));
+ }
+ }
+
+ ArrayList<StateData> stateList = new ArrayList<>();
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(20, stateList.size());
+
+ // Simulate processing all the states.
+ for (int i = 0; i < stateList.size(); i++) {
+ stateList.get(i).mProcessed = true;
+ }
+ // Clear out all processed states.
+ mStateTracker.stateProcessingComplete();
+
+ stateList.clear();
+
+ mStateTracker.retrieveAllStates(stateList);
+
+ assertEquals(0, stateList.size());
+ }
+}
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
index ad95fbc36867..88ebf3edc7ed 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
@@ -2,10 +2,11 @@ package android.security.attestationverification
import android.app.Activity
import android.os.Bundle
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS
import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY
import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE
-import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
import android.security.attestationverification.AttestationVerificationManager.TYPE_UNKNOWN
@@ -54,7 +55,7 @@ class PeerDeviceSystemAttestationVerificationTest {
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -66,7 +67,7 @@ class PeerDeviceSystemAttestationVerificationTest {
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -80,7 +81,7 @@ class PeerDeviceSystemAttestationVerificationTest {
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
val future2 = CompletableFuture<Int>()
val challengeRequirements = Bundle()
@@ -90,7 +91,7 @@ class PeerDeviceSystemAttestationVerificationTest {
future2.complete(result)
}
- assertThat(future2.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future2.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -104,7 +105,7 @@ class PeerDeviceSystemAttestationVerificationTest {
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -118,7 +119,7 @@ class PeerDeviceSystemAttestationVerificationTest {
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS)
}
@Test
@@ -131,7 +132,7 @@ class PeerDeviceSystemAttestationVerificationTest {
invalidAttestationByteArray, activity.mainExecutor) { result, _ ->
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS)
}
private fun <T> CompletableFuture<T>.getSoon(): T {
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
index 8f06b4a2ea0a..e77364de8747 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
@@ -2,6 +2,9 @@ package android.security.attestationverification
import android.os.Bundle
import android.app.Activity
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNSUPPORTED_PROFILE
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -14,9 +17,6 @@ import com.google.common.truth.Truth.assertThat
import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED
import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN
-import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
-import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
-import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN
import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
import android.security.keystore.KeyGenParameterSpec
@@ -58,19 +58,19 @@ class SystemAttestationVerificationTest {
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_UNSUPPORTED_PROFILE)
}
@Test
fun verifyAttestation_returnsFailureWithEmptyAttestation() {
val future = CompletableFuture<Int>()
- val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
- avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0),
- activity.mainExecutor) { result, _ ->
+ val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr")
+ avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType,
+ selfTrusted.requirements, ByteArray(0), activity.mainExecutor) { result, _ ->
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS)
}
@Test
@@ -81,7 +81,7 @@ class SystemAttestationVerificationTest {
Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ ->
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -92,7 +92,7 @@ class SystemAttestationVerificationTest {
selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -106,7 +106,7 @@ class SystemAttestationVerificationTest {
wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -119,7 +119,7 @@ class SystemAttestationVerificationTest {
wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) {
result, _ -> future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE)
+ assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
// TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED.
@@ -131,20 +131,7 @@ class SystemAttestationVerificationTest {
selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ ->
future.complete(result)
}
- assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS)
- }
-
- @Test
- fun verifyToken_returnsUnknown() {
- val future = CompletableFuture<Int>()
- val profile = AttestationProfile(PROFILE_SELF_TRUSTED)
- avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0),
- activity.mainExecutor) { _, token ->
- val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null)
- future.complete(result)
- }
-
- assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN)
+ assertThat(future.getSoon()).isEqualTo(0)
}
@Test
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
index afb3593e3e98..4d1a1a55af74 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -3,10 +3,12 @@ package com.android.server.security
import android.app.Activity
import android.content.Context
import android.os.Bundle
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS
+import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF
import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE
+import android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS
import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY
-import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE
-import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
import android.util.IndentingPrintWriter
@@ -71,7 +73,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_SUCCESS)
+ assertThat(result).isEqualTo(0)
}
@Test
@@ -87,7 +89,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_SUCCESS)
+ assertThat(result).isEqualTo(0)
}
@Test
@@ -107,7 +109,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_PUBLIC_KEY, pkRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_SUCCESS)
+ assertThat(result).isEqualTo(0)
}
@Test
@@ -125,7 +127,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_SUCCESS)
+ assertThat(result).isEqualTo(0)
}
@Test
@@ -142,7 +144,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_FAILURE)
+ assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
@Test
@@ -158,7 +160,60 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_FAILURE)
+ assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF)
+ }
+
+ @Test
+ fun verifyAttestation_returnsSuccessPatchDataWithinMaxPatchDiff() {
+ val verifier = AttestationVerificationPeerDeviceVerifier(
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1),
+ LocalDate.of(2023, 2, 1)
+ )
+ val challengeRequirements = Bundle()
+ challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
+ challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24)
+
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
+ assertThat(result).isEqualTo(0)
+ }
+
+ @Test
+ fun verifyAttestation_returnsFailurePatchDataNotWithinMaxPatchDiff() {
+ val verifier = AttestationVerificationPeerDeviceVerifier(
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1),
+ LocalDate.of(2024, 9, 1)
+ )
+ val challengeRequirements = Bundle()
+ challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
+ challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24)
+
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
+ assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF)
+ }
+
+ @Test
+ fun verifyAttestation_returnsFailureOwnedBySystemAndPatchDataNotWithinMaxPatchDiff() {
+ val verifier = AttestationVerificationPeerDeviceVerifier(
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1),
+ LocalDate.of(2024, 9, 1)
+ )
+ val challengeRequirements = Bundle()
+ challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
+ challengeRequirements.putBoolean("android.key_owned_by_system", true)
+ challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24)
+
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
+ // Both "owned by system" and "patch level diff" checks should fail.
+ assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS or FLAG_FAILURE_PATCH_LEVEL_DIFF)
}
@Test
@@ -174,7 +229,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_FAILURE)
+ assertThat(result).isEqualTo(FLAG_FAILURE_CERTS)
}
@Test
@@ -196,7 +251,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_FAILURE)
+ assertThat(result).isEqualTo(FLAG_FAILURE_CERTS)
}
fun verifyAttestation_returnsFailureChallenge() {
@@ -211,7 +266,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_FAILURE)
+ assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS)
}
private fun String.fromPEMFileToCerts(): Collection<Certificate> {
@@ -245,6 +300,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
companion object {
private const val TAG = "AVFTest"
private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem"
+ // Local patch date is 20220105
private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME =
"test_attestation_with_root_certs.pem"
private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem"
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index 08430f2f2744..30cc002b4144 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@ public class BatteryUsageStatsPerfTest {
private static BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
@@ -171,11 +171,11 @@ public class BatteryUsageStatsPerfTest {
.setConsumedPower(123)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 10100)
- .setConsumedPowerForCustomComponent(
+ .setConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200)
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 10300)
- .setUsageDurationForCustomComponentMillis(
+ .setUsageDurationMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
for (int i = 0; i < 1000; i++) {
@@ -191,10 +191,9 @@ public class BatteryUsageStatsPerfTest {
consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000);
}
- consumerBuilder.setConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
- .setUsageDurationForCustomComponentMillis(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
+ consumerBuilder
+ .setConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
+ .setUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
}
return builder.build();
}
diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp
index 9fb5aa21f985..dbdc4b4407b7 100644
--- a/tests/BootImageProfileTest/Android.bp
+++ b/tests/BootImageProfileTest/Android.bp
@@ -19,6 +19,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_art_mainline",
}
java_test_host {
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index e09fbf6adc02..529f84ac4e90 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -24,61 +24,116 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-filegroup {
- name: "FlickerTestsOtherCommon-src",
- srcs: ["src/**/ActivityEmbeddingTestBase.kt"],
+android_test {
+ name: "FlickerTestsActivityEmbedding",
+ defaults: ["FlickerTestsDefault"],
+ manifest: "AndroidManifest.xml",
+ package_name: "com.android.server.wm.flicker",
+ instrumentation_target_package: "com.android.server.wm.flicker",
+ test_config_template: "AndroidTestTemplate.xml",
+ srcs: ["src/**/*"],
+ static_libs: ["FlickerTestsBase"],
+ data: ["trace_config/*"],
}
-filegroup {
- name: "FlickerTestsOtherOpen-src",
- srcs: ["src/**/open/*"],
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsActivityEmbedding module
+
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-CatchAll",
+ base: "FlickerTestsActivityEmbedding",
+ exclude_filters: [
+ "com.android.server.wm.flicker.activityembedding.close.CloseSecondaryActivityInSplitTest",
+ "com.android.server.wm.flicker.activityembedding.layoutchange.HorizontalSplitChangeRatioTest",
+ "com.android.server.wm.flicker.activityembedding.open.MainActivityStartsSecondaryWithAlwaysExpandTest",
+ "com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingPlaceholderSplitTest",
+ "com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingSecondaryToSplitTest",
+ "com.android.server.wm.flicker.activityembedding.open.OpenThirdActivityOverSplitTest",
+ "com.android.server.wm.flicker.activityembedding.open.OpenTrampolineActivityTest",
+ "com.android.server.wm.flicker.activityembedding.pip.SecondaryActivityEnterPipTest",
+ "com.android.server.wm.flicker.activityembedding.rotation.RotateSplitNoChangeTest",
+ "com.android.server.wm.flicker.activityembedding.rtl.RTLStartSecondaryWithPlaceholderTest",
+ "com.android.server.wm.flicker.activityembedding.splitscreen.EnterSystemSplitTest",
+ ],
+ test_suites: ["device-tests"],
}
-filegroup {
- name: "FlickerTestsOtherRotation-src",
- srcs: ["src/**/rotation/*"],
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Close-CloseSecondaryActivityInSplitTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.close.CloseSecondaryActivityInSplitTest"],
+ test_suites: ["device-tests"],
}
-java_library {
- name: "FlickerTestsOtherCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsOtherCommon-src"],
- static_libs: ["FlickerTestsBase"],
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-LayoutChange-HorizontalSplitChangeRatioTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.layoutchange.HorizontalSplitChangeRatioTest"],
+ test_suites: ["device-tests"],
}
-java_defaults {
- name: "FlickerTestsOtherDefaults",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.server.wm.flicker",
- instrumentation_target_package: "com.android.server.wm.flicker",
- test_config_template: "AndroidTestTemplate.xml",
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsOtherCommon",
- ],
- data: ["trace_config/*"],
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Open-MainActivityStartsSecondaryWithAlwaysExpandTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.open.MainActivityStartsSecondaryWithAlwaysExpandTest"],
+ test_suites: ["device-tests"],
}
-android_test {
- name: "FlickerTestsOtherOpen",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: [":FlickerTestsOtherOpen-src"],
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Open-OpenActivityEmbeddingPlaceholderSplitTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingPlaceholderSplitTest"],
+ test_suites: ["device-tests"],
}
-android_test {
- name: "FlickerTestsOtherRotation",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: [":FlickerTestsOtherRotation-src"],
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Open-OpenActivityEmbeddingSecondaryToSplitTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenActivityEmbeddingSecondaryToSplitTest"],
+ test_suites: ["device-tests"],
}
-android_test {
- name: "FlickerTestsOther",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsOtherOpen-src",
- ":FlickerTestsOtherRotation-src",
- ":FlickerTestsOtherCommon-src",
- ],
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Open-OpenThirdActivityOverSplitTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenThirdActivityOverSplitTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Open-OpenTrampolineActivityTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.open.OpenTrampolineActivityTest"],
+ test_suites: ["device-tests"],
}
+
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Pip-SecondaryActivityEnterPipTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.pip.SecondaryActivityEnterPipTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Rotation-RotateSplitNoChangeTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.rotation.RotateSplitNoChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-Rtl-RTLStartSecondaryWithPlaceholderTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.rtl.RTLStartSecondaryWithPlaceholderTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsActivityEmbedding-SplitScreen-EnterSystemSplitTest",
+ base: "FlickerTestsActivityEmbedding",
+ include_filters: ["com.android.server.wm.flicker.activityembedding.splitscreen.EnterSystemSplitTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsActivityEmbedding module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 82de070921f0..8b65efdfb5f9 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
index 519b4296d93a..f44e282e8116 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
* Setup: Launch A|B in split with B being the secondary activity. Transitions: Finish B and expect
* A to become fullscreen.
*
- * To run this test: `atest FlickerTestsOther:CloseSecondaryActivityInSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:CloseSecondaryActivityInSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index 4cd6d15b2983..7a76dd9d1ebb 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -39,7 +39,7 @@ import org.junit.runners.Parameterized
* windows are equal in size. B is on the top and A is on the bottom. Transitions: Change the split
* ratio to A:B=0.7:0.3, expect bounds change for both A and B.
*
- * To run this test: `atest FlickerTestsOther:HorizontalSplitChangeRatioTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:HorizontalSplitChangeRatioTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 5df8b57294f0..08b5f38a4655 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -39,7 +39,7 @@ import org.junit.runners.Parameterized
* Setup: Launch A|B in split with B being the secondary activity. Transitions: A start C with
* alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
*
- * To run this test: `atest FlickerTestsOther:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:MainActivityStartsSecondaryWithAlwaysExpandTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
index 5009c7ce4e70..1f002a089486 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
* Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
* split.
*
- * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingPlaceholderSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenActivityEmbeddingPlaceholderSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
index 6327d92ed570..b78c3ec65e32 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test opening a secondary activity that will split with the main activity.
*
- * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingSecondaryToSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenActivityEmbeddingSecondaryToSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
index 78004ccc3f97..10167d71c255 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -39,7 +39,7 @@ import org.junit.runners.Parameterized
*
* Transitions: Let B start C, expect C to cover B and end up in split A|C.
*
- * To run this test: `atest FlickerTestsOther:OpenThirdActivityOverSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenThirdActivityOverSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index 67825d2df361..3753b23966d2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -23,7 +23,6 @@ import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.flicker.subject.region.RegionSubject
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
@@ -41,9 +40,8 @@ import org.junit.runners.Parameterized
* Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and finishes
* itself, end up in split A|B.
*
- * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:OpenTrampolineActivityTest`
*/
-@FlakyTest(bugId = 341209752)
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index eed9225d3da0..a0b910bb9ac3 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
* Setup: Start from a split A|B. Transition: B enters PIP, observe the window first goes fullscreen
* then shrink to the bottom right corner on screen.
*
- * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:SecondaryActivityEnterPipTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index f5e6c7854eba..ea13f5f748e1 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -36,7 +36,7 @@ import org.junit.runners.Parameterized
* Setup: Launch A|B in split with B being the secondary activity. Transitions: Rotate display, and
* expect A and B to split evenly in new rotation.
*
- * To run this test: `atest FlickerTestsOther:RotateSplitNoChangeTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:RotateSplitNoChangeTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index ee2c05e82d51..06326f8cc8d2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -36,7 +36,7 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin
teardown { testApp.exit(wmHelper) }
transitions {
this.setRotation(flicker.scenario.endRotation)
- if (!flicker.scenario.isTablet) {
+ if (!usesTaskbar) {
wmHelper.StateSyncBuilder()
.add(navBarInPosition(flicker.scenario.isGesturalNavigation))
.waitForAndVerify()
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
index 65a23e854e0b..2a177d53b037 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
@@ -37,7 +37,7 @@ import org.junit.runners.Parameterized
* PlaceholderPrimary, which is configured to launch with PlaceholderSecondary in RTL. Expect split
* PlaceholderSecondary|PlaceholderPrimary covering split B|A.
*
- * To run this test: `atest FlickerTestsOther:RTLStartSecondaryWithPlaceholderTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:RTLStartSecondaryWithPlaceholderTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 379b45cdf08e..0ca8f37b239b 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -24,6 +24,7 @@ import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -46,7 +47,7 @@ import org.junit.runners.Parameterized
* Setup: Launch A|B in split and secondaryApp, return to home. Transitions: Let AE Split A|B enter
* splitscreen with secondaryApp. Resulting in A|B|secondaryApp.
*
- * To run this test: `atest FlickerTestsOther:EnterSystemSplitTest`
+ * To run this test: `atest FlickerTestsActivityEmbedding:EnterSystemSplitTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -177,6 +178,13 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa
@Ignore("Not applicable to this CUJ.")
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
+ @FlakyTest(bugId = 342596801)
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ @FlakyTest(bugId = 342596801)
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
/** {@inheritDoc} */
private var startDisplayBounds = Rect()
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 27e9ffa4cea5..1e997b386faa 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -47,7 +47,6 @@ java_defaults {
java_library {
name: "wm-flicker-common-assertions",
- platform_apis: true,
optimize: {
enabled: false,
},
diff --git a/tests/FlickerTests/AppClose/Android.bp b/tests/FlickerTests/AppClose/Android.bp
index d14a178fe316..8b45740aad7b 100644
--- a/tests/FlickerTests/AppClose/Android.bp
+++ b/tests/FlickerTests/AppClose/Android.bp
@@ -33,3 +33,34 @@ android_test {
static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsAppClose module
+
+test_module_config {
+ name: "FlickerTestsAppClose-CatchAll",
+ base: "FlickerTestsAppClose",
+ exclude_filters: [
+ "com.android.server.wm.flicker.close.CloseAppBackButtonTest",
+ "com.android.server.wm.flicker.close.CloseAppHomeButtonTest",
+ "com.android.server.wm.flicker.close.",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppClose-CloseAppBackButtonTest",
+ base: "FlickerTestsAppClose",
+ include_filters: ["com.android.server.wm.flicker.close.CloseAppBackButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppClose-CloseAppHomeButtonTest",
+ base: "FlickerTestsAppClose",
+ include_filters: ["com.android.server.wm.flicker.close.CloseAppHomeButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsAppClose module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4ffb11ab92ae..3382c1e227b3 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index e19e1ce35cd9..56b718a642db 100644
--- a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -31,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test app closes by pressing back button
*
- * To run this test: `atest FlickerTests:CloseAppBackButtonTest`
+ * To run this test: `atest FlickerTestsAppClose:CloseAppBackButtonTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 47ed642cd5f5..5deacaf31802 100644
--- a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -31,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test app closes by pressing home button
*
- * To run this test: `atest FlickerTests:CloseAppHomeButtonTest`
+ * To run this test: `atest FlickerTestsAppClose:CloseAppHomeButtonTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/Android.bp b/tests/FlickerTests/AppLaunch/Android.bp
index 72a90650927f..17d0f967b1bd 100644
--- a/tests/FlickerTests/AppLaunch/Android.bp
+++ b/tests/FlickerTests/AppLaunch/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_windowing_animations_transitions",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -23,49 +24,115 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-filegroup {
- name: "FlickerTestsAppLaunchCommon-src",
- srcs: ["src/**/common/*"],
-}
-
-filegroup {
- name: "FlickerTestsAppLaunch1-src",
- srcs: ["src/**/OpenAppFrom*"],
-}
-
-java_library {
- name: "FlickerTestsAppLaunchCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsAppLaunchCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
-
android_test {
- name: "FlickerTestsAppLaunch1",
+ name: "FlickerTestsAppLaunch",
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
test_config_template: "AndroidTestTemplate.xml",
- srcs: [":FlickerTestsAppLaunch1-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
- ],
+ srcs: ["src/**/*"],
+ static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
-android_test {
- name: "FlickerTestsAppLaunch2",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsAppLaunchCommon-src",
- ":FlickerTestsAppLaunch1-src",
- ],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsAppLaunch module
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-CatchAll",
+ base: "FlickerTestsAppLaunch",
+ exclude_filters: [
+ "com.android.server.wm.flicker.launch.TaskTransitionTest",
+ "com.android.server.wm.flicker.launch.ActivityTransitionTest",
+ "com.android.server.wm.flicker.launch.OpenAppFromIconColdTest",
+ "com.android.server.wm.flicker.launch.OpenAppFromIntentColdAfterCameraTest",
+ "com.android.server.wm.flicker.launch.OpenAppFromIntentColdTest",
+ "com.android.server.wm.flicker.launch.OpenAppFromIntentWarmTest",
+ "com.android.server.wm.flicker.launch.OpenAppFromLockscreenViaIntentTest",
+ "com.android.server.wm.flicker.launch.OpenAppFromOverviewTest",
+ "com.android.server.wm.flicker.launch.OpenCameraFromHomeOnDoubleClickPowerButtonTest",
+ "com.android.server.wm.flicker.launch.OpenTransferSplashscreenAppFromLauncherTransition",
+ "com.android.server.wm.flicker.launch.OverrideTaskTransitionTest",
+ "com.android.server.wm.flicker.launch.TaskTransitionTest",
],
- data: ["trace_config/*"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-ActivityTransitionTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.ActivityTransitionTest"],
+ test_suites: ["device-tests"],
}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenAppFromIconColdTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIconColdTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenAppFromIntentColdAfterCameraTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentColdAfterCameraTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenAppFromIntentColdTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentColdTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenAppFromIntentWarmTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromIntentWarmTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenAppFromLockscreenViaIntentTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromLockscreenViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenAppFromOverviewTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenAppFromOverviewTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenCameraFromHomeOnDoubleClickPowerButtonTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenCameraFromHomeOnDoubleClickPowerButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OpenTransferSplashscreenAppFromLauncherTransition",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OpenTransferSplashscreenAppFromLauncherTransition"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-OverrideTaskTransitionTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.OverrideTaskTransitionTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppLaunch-TaskTransitionTest",
+ base: "FlickerTestsAppLaunch",
+ include_filters: ["com.android.server.wm.flicker.launch.TaskTransitionTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsAppLaunch module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 0fa4d07b2eca..e941e79faea3 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppLaunch/OWNERS b/tests/FlickerTests/AppLaunch/OWNERS
index 2c414a27cacb..d16b57dcb84c 100644
--- a/tests/FlickerTests/AppLaunch/OWNERS
+++ b/tests/FlickerTests/AppLaunch/OWNERS
@@ -1,4 +1,2 @@
-# System UI > ... > Overview (recent apps) > UI
-# Bug template url: https://b.corp.google.com/issues/new?component=807991&template=1390280 = per-file *Overview*
# window manager > animations/transitions
# Bug template url: https://b.corp.google.com/issues/new?component=316275&template=1018192
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
index ffa90a33e7b3..01cdbb810379 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
@@ -35,7 +35,7 @@ import org.junit.runners.Parameterized
/**
* Test the back and forward transition between 2 activities.
*
- * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ * To run this test: `atest FlickerTestsAppLaunch:ActivitiesTransitionTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 8c285bda6616..3d9321c0b830 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -30,7 +30,7 @@ import org.junit.runners.Parameterized
/**
* Test cold launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppColdFromIcon`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index 57da05f13bbb..92075303028c 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -30,7 +30,7 @@ import org.junit.runners.Parameterized
/**
* Test launching an app after cold opening camera
*
- * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppAfterCameraTest`
*
* Notes: Some default assertions are inherited [OpenAppTransition]
*/
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 267f282db41c..cbe7c3241df3 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -35,7 +35,7 @@ import org.junit.runners.Parameterized
/**
* Test cold launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenAppColdTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppColdTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 83065de8b592..b2941e70a2ed 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test warm launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenAppWarmTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppWarmTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 44ae27c2ee4b..4048e0c89619 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
*
* This test assumes the device doesn't have AOD enabled
*
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppNonResizeableTest`
*
* Actions:
* ```
@@ -75,7 +75,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
@FlakyTest(bugId = 288341660)
@Test
fun navBarLayerVisibilityChanges() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.assertLayers {
this.isInvisible(ComponentNameMatcher.NAV_BAR)
.then()
@@ -97,7 +97,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
@FlakyTest(bugId = 293581770)
@Test
fun navBarWindowsVisibilityChanges() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.assertWm {
this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
.then()
@@ -112,7 +112,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -170,7 +170,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -184,7 +184,7 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
override fun appLayerBecomesVisible() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
super.appLayerBecomesVisible()
}
@@ -192,11 +192,11 @@ open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
@FlakyTest(bugId = 227143265)
@Test
fun appLayerBecomesVisibleTablet() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
super.appLayerBecomesVisible()
}
- @Presubmit
+ @FlakyTest(bugId = 338296297)
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 6d3eaeb9c1b3..064c76f1b92f 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -35,7 +35,7 @@ import org.junit.runners.Parameterized
/**
* Test launching an app from the recents app view (the overview)
*
- * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromOverviewTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index bec02d0e59c6..41423fd9eefe 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
/**
* Test cold launching camera from launcher by double pressing power button
*
- * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenCameraOnDoubleClickPowerButton`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
index e0aef8d1addd..9d7a9c6789f8 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test cold launching an app from launcher
*
- * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ * To run this test: `atest FlickerTestsAppLaunch:OpenTransferSplashscreenAppFromLauncherTransition`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index f1144991c438..7e2d472f4c4d 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -42,7 +42,7 @@ import org.junit.runners.Parameterized
/**
* Test the [android.app.ActivityOptions.makeCustomTaskAnimation].
*
- * To run this test: `atest FlickerTests:OverrideTaskTransitionTest`
+ * To run this test: `atest FlickerTestsAppLaunch:OverrideTaskTransitionTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index a71599d25632..95e8126964e7 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -49,7 +49,7 @@ import org.junit.runners.Parameterized
/**
* Test the back and forward transition between 2 activities.
*
- * To run this test: `atest FlickerTests:TaskTransitionTest`
+ * To run this test: `atest FlickerTestsAppLaunch:TaskTransitionTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
index 8a3304b0343d..b497e3048759 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
@@ -28,6 +28,7 @@ abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) :
get() = {
super.transition(this)
setup {
+ // By default, launcher doesn't rotate on phones, but rotates on tablets
if (flicker.scenario.isTablet) {
tapl.setExpectedRotation(flicker.scenario.startRotation.value)
} else {
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index f8fd35860f6f..a6e31d49a0e8 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -103,7 +103,7 @@ abstract class OpenAppFromLockscreenTransition(flicker: LegacyFlickerTest) :
@Presubmit
@Test
open fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerPositionAtEnd()
}
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4d9fefbc7d88..4e06dca17fe2 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
index 8040610c485b..cfc818b6c0e9 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButton3ButtonLandscape :
CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
index aacccf4e680c..6bf32a8e2083 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButton3ButtonPortrait :
CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
index 74ee46093f6e..4b6ab773f15e 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButtonGesturalNavLandscape :
CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
index 57463c33c1fa..7cc9db027e1f 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -31,8 +31,7 @@ import org.junit.runner.RunWith
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButtonGesturalNavPortrait :
CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
- // TODO: Missing CUJ (b/300078127)
- @ExpectedScenarios(["ENTIRE_TRACE"])
+ @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index ccc3683f0b93..cba3d09ebefd 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -24,16 +24,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-filegroup {
- name: "FlickerTestsImeCommon-src",
- srcs: ["src/**/common/*"],
-}
-
-filegroup {
- name: "FlickerTestsIme1-src",
- srcs: ["src/**/Close*"],
-}
-
android_test {
name: "FlickerTestsIme",
defaults: ["FlickerTestsDefault"],
@@ -48,43 +38,136 @@ android_test {
data: ["trace_config/*"],
}
-java_library {
- name: "FlickerTestsImeCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsImeCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsIme module
-android_test {
- name: "FlickerTestsIme1",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- test_suites: [
- "device-tests",
- "device-platinum-tests",
+test_module_config {
+ name: "FlickerTestsIme-CatchAll",
+ base: "FlickerTestsIme",
+ exclude_filters: [
+ "com.android.server.wm.flicker.ime.CloseImeOnDismissPopupDialogTest",
+ "com.android.server.wm.flicker.ime.CloseImeOnGoHomeTest",
+ "com.android.server.wm.flicker.ime.CloseImeShownOnAppStartOnGoHomeTest",
+ "com.android.server.wm.flicker.ime.CloseImeShownOnAppStartToAppOnPressBackTest",
+ "com.android.server.wm.flicker.ime.CloseImeToAppOnPressBackTest",
+ "com.android.server.wm.flicker.ime.CloseImeToHomeOnFinishActivityTest",
+ "com.android.server.wm.flicker.ime.OpenImeWindowToFixedPortraitAppTest",
+ "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest",
+ "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromOverviewTest",
+ "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest",
+ "com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppTest",
+ "com.android.server.wm.flicker.ime.ShowImeOnUnlockScreenTest",
+ "com.android.server.wm.flicker.ime.ShowImeWhenFocusingOnInputFieldTest",
+ "com.android.server.wm.flicker.ime.ShowImeWhileDismissingThemedPopupDialogTest",
+ "com.android.server.wm.flicker.ime.ShowImeWhileEnteringOverviewTest",
],
- srcs: [":FlickerTestsIme1-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
+ test_suites: ["device-tests"],
}
-android_test {
- name: "FlickerTestsIme2",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsIme1-src",
- ":FlickerTestsImeCommon-src",
- ],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
+test_module_config {
+ name: "FlickerTestsIme-CloseImeOnDismissPopupDialogTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.CloseImeOnDismissPopupDialogTest"],
+ test_suites: ["device-tests"],
}
+
+test_module_config {
+ name: "FlickerTestsIme-CloseImeOnGoHomeTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.CloseImeOnGoHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-CloseImeShownOnAppStartOnGoHomeTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.CloseImeShownOnAppStartOnGoHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-CloseImeShownOnAppStartToAppOnPressBackTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.CloseImeShownOnAppStartToAppOnPressBackTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-CloseImeToAppOnPressBackTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.CloseImeToAppOnPressBackTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-CloseImeToHomeOnFinishActivityTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.CloseImeToHomeOnFinishActivityTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-OpenImeWindowToFixedPortraitAppTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.OpenImeWindowToFixedPortraitAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppFromOverviewTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromOverviewTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeOnAppStartWhenLaunchingAppTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnAppStartWhenLaunchingAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeOnUnlockScreenTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeOnUnlockScreenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeWhenFocusingOnInputFieldTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeWhenFocusingOnInputFieldTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeWhileDismissingThemedPopupDialogTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeWhileDismissingThemedPopupDialogTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsIme-ShowImeWhileEnteringOverviewTest",
+ base: "FlickerTestsIme",
+ include_filters: ["com.android.server.wm.flicker.ime.ShowImeWhileEnteringOverviewTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsIme module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b879c54dcab3..0cadd68597b6 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- enable AOD -->
<option name="set-secure-setting" key="doze_always_on" value="1" />
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 2b6ddcb43f18..48ca36ff8012 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -33,7 +33,7 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeOnDismissPopupDialogTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 0344197c1425..e3f3aca135d1 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window closing to home transitions.
- * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeOnGoHomeTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index fde1373b032b..3509e5bfecaf 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -42,7 +42,7 @@ import org.junit.runners.Parameterized
*
* More details on b/190352379
*
- * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeShownOnAppStartOnGoHomeTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index ed6e8df3e293..53d7a3ff8f21 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -43,7 +43,7 @@ import org.junit.runners.Parameterized
*
* More details on b/190352379
*
- * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeShownOnAppStartToAppOnPressBackTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index dc2bd1bc9996..4bc2705aa17d 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -35,7 +35,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window closing back to app window transitions.
- * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeToAppOnPressBackTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -72,7 +72,7 @@ class CloseImeToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicke
@Presubmit
@Test
override fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
flicker.navBarLayerPositionAtStartAndEnd()
}
@@ -80,7 +80,7 @@ class CloseImeToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicke
@Presubmit
@Test
fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
flicker.navBarLayerPositionAtStartAndEnd()
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 05771e88fc83..6117bb0971d0 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -40,7 +40,7 @@ import org.junit.runners.Parameterized
* Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify
* there is no flickering when back to the simple activity without requesting IME to show.
*
- * To run this test: `atest FlickerTestsIme1:CloseImeToHomeOnFinishActivityTest`
+ * To run this test: `atest FlickerTestsIme:CloseImeToHomeOnFinishActivityTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 336fe6f991ca..9b8d86d82007 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -37,7 +37,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window shown on the app with fixing portrait orientation.
- * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest`
+ * To run this test: `atest FlickerTestsIme:OpenImeWindowToFixedPortraitAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 34a708578396..f806fae01eb4 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window opening transitions.
- * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 7c72c3187a7f..cc19f62a7cb3 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -36,7 +36,7 @@ import org.junit.runners.Parameterized
/**
* Test IME windows switching with 2-Buttons or gestural navigation.
- * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index fe5320cd1a46..4a4d3725d82c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -36,7 +36,7 @@ import org.junit.runners.Parameterized
/**
* Launch an app that automatically displays the IME
*
- * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
index 92b6b934874f..d47e7ad8d904 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt
@@ -36,7 +36,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window closing on lock and opening on screen unlock.
- * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeOnUnlockScreenTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -54,7 +54,7 @@ class ShowImeOnUnlockScreenTest(flicker: LegacyFlickerTest) : BaseTest(flicker)
}
transitions {
device.sleep()
- wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+ wmHelper.StateSyncBuilder().withKeyguardShowing().waitForAndVerify()
UnlockScreenRule.unlockScreen(device)
wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 9eaf998ed63f..47bf32483d69 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -33,7 +33,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window opening transitions.
- * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeWhenFocusingOnInputFieldTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 7186a2c48c4c..e3118b4cae0c 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -41,7 +41,7 @@ import org.junit.runners.Parameterized
/**
* Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
- * To run this test: `atest FlickerTestsIme2:ShowImeWhileDismissingThemedPopupDialogTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeWhileDismissingThemedPopupDialogTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index c96c760e2d7b..064c07ea0dc0 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -28,6 +28,7 @@ import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -38,7 +39,7 @@ import org.junit.runners.Parameterized
/**
* Test IME window layer will be associated with the app task when going to the overview screen.
- * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest`
+ * To run this test: `atest FlickerTestsIme:ShowImeWhileEnteringOverviewTest`
*/
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -93,7 +94,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
@Presubmit
@Test
fun navBarLayerIsVisibleAtStartAndEnd3Button() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
flicker.navBarLayerIsVisibleAtStartAndEnd()
}
@@ -105,7 +106,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
@Presubmit
@Test
fun navBarLayerIsInvisibleInLandscapeGestural() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
@@ -114,7 +115,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
/**
* In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
- * this is fixed and the nav bar shows as invisible
+ * this is fixed and the status bar shows as invisible
*/
@Presubmit
@Test
@@ -128,7 +129,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
/**
* In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
- * this is fixed and the nav bar shows as invisible
+ * this is fixed and the status bar shows as invisible
*/
@Presubmit
@Test
@@ -149,6 +150,10 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
@Ignore("Visibility changes depending on orientation and navigation mode")
override fun navBarLayerPositionAtStartAndEnd() {}
+ @Test
+ @Ignore("Visibility changes depending on orientation and navigation mode")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
@@ -161,7 +166,10 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+ fun taskBarLayerIsVisibleAtStartAndEndForTablets() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
+ }
@Presubmit
@Test
@@ -174,7 +182,7 @@ class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(fl
@Test
fun statusBarLayerIsInvisibleInLandscape() {
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
diff --git a/tests/FlickerTests/Notification/Android.bp b/tests/FlickerTests/Notification/Android.bp
index 4648383b2771..06daaafacbd8 100644
--- a/tests/FlickerTests/Notification/Android.bp
+++ b/tests/FlickerTests/Notification/Android.bp
@@ -32,3 +32,57 @@ android_test {
static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsNotification module
+
+test_module_config {
+ name: "FlickerTestsNotification-CatchAll",
+ base: "FlickerTestsNotification",
+ exclude_filters: [
+ "com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationColdTest",
+ "com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWarmTest",
+ "com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWithOverlayAppTest",
+ "com.android.server.wm.flicker.notification.OpenAppFromNotificationColdTest",
+ "com.android.server.wm.flicker.notification.OpenAppFromNotificationWarmTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsNotification-OpenAppFromLockscreenNotificationColdTest",
+ base: "FlickerTestsNotification",
+ include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationColdTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsNotification-OpenAppFromLockscreenNotificationWarmTest",
+ base: "FlickerTestsNotification",
+ include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWarmTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsNotification-OpenAppFromLockscreenNotificationWithOverlayAppTest",
+ base: "FlickerTestsNotification",
+ include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromLockscreenNotificationWithOverlayAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsNotification-OpenAppFromNotificationColdTest",
+ base: "FlickerTestsNotification",
+ include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromNotificationColdTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsNotification-OpenAppFromNotificationWarmTest",
+ base: "FlickerTestsNotification",
+ include_filters: ["com.android.server.wm.flicker.notification.OpenAppFromNotificationWarmTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsNotification module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 04b312a896b9..f32e8bed85ef 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index 07fc2300286a..ad70757a9a4d 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -151,7 +151,7 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
open fun taskBarWindowIsVisibleAtEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.taskBarWindowIsVisibleAtEnd()
}
@@ -163,7 +163,7 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.taskBarLayerIsVisibleAtEnd()
}
@@ -171,7 +171,7 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
open fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerPositionAtEnd()
}
@@ -179,14 +179,14 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
@Presubmit
@Test
open fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerIsVisibleAtEnd()
}
@Presubmit
@Test
open fun navBarWindowIsVisibleAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarWindowIsVisibleAtEnd()
}
diff --git a/tests/FlickerTests/QuickSwitch/Android.bp b/tests/FlickerTests/QuickSwitch/Android.bp
index 8755d0e3b304..4d5dba3d9221 100644
--- a/tests/FlickerTests/QuickSwitch/Android.bp
+++ b/tests/FlickerTests/QuickSwitch/Android.bp
@@ -32,3 +32,41 @@ android_test {
static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsQuickswitch module
+
+test_module_config {
+ name: "FlickerTestsQuickswitch-CatchAll",
+ base: "FlickerTestsQuickswitch",
+ exclude_filters: [
+ "com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsBackTest",
+ "com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsForwardTest",
+ "com.android.server.wm.flicker.quickswitch.QuickSwitchFromLauncherTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsQuickswitch-QuickSwitchBetweenTwoAppsBackTest",
+ base: "FlickerTestsQuickswitch",
+ include_filters: ["com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsBackTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsQuickswitch-QuickSwitchBetweenTwoAppsForwardTest",
+ base: "FlickerTestsQuickswitch",
+ include_filters: ["com.android.server.wm.flicker.quickswitch.QuickSwitchBetweenTwoAppsForwardTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsQuickswitch-QuickSwitchFromLauncherTest",
+ base: "FlickerTestsQuickswitch",
+ include_filters: ["com.android.server.wm.flicker.quickswitch.QuickSwitchFromLauncherTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsQuickswitch module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 8acdabc2337d..68ae4f1f7f4f 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 9bb62e1e1794..1a32f2045546 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
/**
* Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchBetweenTwoAppsBackTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 491b9945d12d..d82dddd9eeeb 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -37,7 +37,7 @@ import org.junit.runners.Parameterized
/**
* Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
+ * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchBetweenTwoAppsForwardTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index de54c95da361..ab366286b6d8 100644
--- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
/**
* Test quick switching to last opened app from launcher
*
- * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest`
+ * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchFromLauncherTest`
*
* Actions:
* ```
diff --git a/tests/FlickerTests/Rotation/Android.bp b/tests/FlickerTests/Rotation/Android.bp
index aceb8bad256f..0884ef9734b0 100644
--- a/tests/FlickerTests/Rotation/Android.bp
+++ b/tests/FlickerTests/Rotation/Android.bp
@@ -37,3 +37,41 @@ android_test {
static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsRotation module
+
+test_module_config {
+ name: "FlickerTestsAppRotation-CatchAll",
+ base: "FlickerTestsRotation",
+ exclude_filters: [
+ "com.android.server.wm.flicker.rotation.ChangeAppRotationTest",
+ "com.android.server.wm.flicker.rotation.OpenShowWhenLockedSeamlessAppRotationTest",
+ "com.android.server.wm.flicker.rotation.SeamlessAppRotationTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppRotation-ChangeAppRotationTest",
+ base: "FlickerTestsRotation",
+ include_filters: ["com.android.server.wm.flicker.rotation.ChangeAppRotationTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppRotation-OpenShowWhenLockedSeamlessAppRotationTest",
+ base: "FlickerTestsRotation",
+ include_filters: ["com.android.server.wm.flicker.rotation.OpenShowWhenLockedSeamlessAppRotationTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "FlickerTestsAppRotation-SeamlessAppRotationTest",
+ base: "FlickerTestsRotation",
+ include_filters: ["com.android.server.wm.flicker.rotation.SeamlessAppRotationTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 91ece214aad5..ec186723b4a4 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 05ab364ed72c..49e2553ab4a1 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -48,7 +48,7 @@ import org.junit.runners.Parameterized
* Stop tracing
* ```
*
- * To run this test: `atest FlickerTests:ChangeAppRotationTest`
+ * To run this test: `atest FlickerTestsRotation:ChangeAppRotationTest`
*
* To run only the presubmit assertions add: `--
*
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a41362857420..d7f91e009c92 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -55,7 +55,7 @@ import org.junit.runners.Parameterized
* Stop tracing
* ```
*
- * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
+ * To run this test: `atest FlickerTestsRotation:SeamlessAppRotationTest`
*
* To run only the presubmit assertions add: `--
*
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 060015bcc4b2..851ce022bd81 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -26,6 +26,7 @@ import android.tools.traces.component.ComponentNameMatcher
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.Flags
import org.junit.Assume
import org.junit.AssumptionViolatedException
import org.junit.Test
@@ -48,6 +49,9 @@ constructor(
private val logTag = this::class.java.simpleName
+ protected val usesTaskbar: Boolean
+ get() = flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()
+
/** Specification of the test transition to execute */
abstract val transition: FlickerBuilder.() -> Unit
@@ -87,7 +91,7 @@ constructor(
@Presubmit
@Test
open fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerIsVisibleAtStartAndEnd()
}
@@ -100,7 +104,7 @@ constructor(
@Presubmit
@Test
open fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerPositionAtStartAndEnd()
}
@@ -112,7 +116,7 @@ constructor(
@Presubmit
@Test
open fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
flicker.navBarWindowIsAlwaysVisible()
}
@@ -126,32 +130,28 @@ constructor(
@Presubmit
@Test
open fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarWindowIsVisibleAtStartAndEnd()
}
/**
* Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the
* transition
- *
- * Note: Large screen only
*/
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.taskBarLayerIsVisibleAtStartAndEnd()
}
/**
* Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
- *
- * Note: Large screen only
*/
@Presubmit
@Test
open fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.taskBarWindowIsAlwaysVisible()
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/Android.bp b/tests/FlickerTests/test-apps/app-helpers/Android.bp
index fc4d71c652d5..e8bb64aa6c55 100644
--- a/tests/FlickerTests/test-apps/app-helpers/Android.bp
+++ b/tests/FlickerTests/test-apps/app-helpers/Android.bp
@@ -25,7 +25,6 @@ package {
java_library {
name: "wm-flicker-common-app-helpers",
- platform_apis: true,
optimize: {
enabled: false,
},
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 4a675be65549..0bcd2f334c32 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
+import android.os.SystemClock
import android.tools.PlatformConsts
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.helpers.FIND_TIMEOUT
@@ -25,6 +26,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.parsers.toFlickerComponent
import android.util.Log
import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
import androidx.window.extensions.WindowExtensions
import androidx.window.extensions.WindowExtensionsProvider
@@ -83,6 +85,7 @@ constructor(
* activity and finish itself.
*/
fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
+ scrollToBottom()
val launchButton =
uiDevice.wait(
Until.findObject(By.res(packageName, "launch_trampoline_button")),
@@ -210,6 +213,7 @@ constructor(
* placeholder secondary activity based on the placeholder rule.
*/
fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) {
+ scrollToBottom()
val launchButton =
uiDevice.wait(
Until.findObject(By.res(packageName, "launch_placeholder_split_rtl_button")),
@@ -224,6 +228,21 @@ constructor(
.waitForAndVerify()
}
+ /**
+ * Scrolls to the bottom of the launch options. This is needed if the launch button is at the
+ * bottom. Otherwise the click may trigger touch on navBar.
+ */
+ private fun scrollToBottom() {
+ val launchOptionsList = uiDevice.wait(
+ Until.findObject(By.res(packageName, "launch_options_list")),
+ FIND_TIMEOUT
+ )
+ requireNotNull(launchOptionsList) { "Unable to find the list of launch options" }
+ launchOptionsList.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN))
+ // Wait a bit after scrolling, otherwise the immediate click may not be treated as "click".
+ SystemClock.sleep(1000L)
+ }
+
companion object {
private const val TAG = "ActivityEmbeddingAppHelper"
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9a5e88becf1e..332b9b832037 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -16,16 +16,27 @@
package com.android.server.wm.flicker.helpers
+import android.content.Context
+import android.graphics.Insets
import android.graphics.Rect
+import android.graphics.Region
+import android.os.SystemClock
+import android.platform.uiautomator_helpers.DeviceHelpers
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.wm.WindowingMode
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH
+import com.android.window.flags.Flags
+import java.time.Duration
/**
* Wrapper class around App helper classes. This class adds functionality to the apps that the
@@ -41,15 +52,17 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
RIGHT_BOTTOM
}
- private val TIMEOUT_MS = 3_000L
- private val CAPTION = "desktop_mode_caption"
- private val CAPTION_HANDLE = "caption_handle"
- private val MAXIMIZE_BUTTON = "maximize_window"
- private val MAXIMIZE_BUTTON_VIEW = "maximize_button_view"
- private val CLOSE_BUTTON = "close_window"
+ enum class Edges {
+ LEFT,
+ RIGHT,
+ TOP,
+ BOTTOM
+ }
- private val caption: BySelector
- get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+ enum class AppProperty {
+ STANDARD,
+ NON_RESIZABLE
+ }
/** Wait for an app moved to desktop to finish its transition. */
private fun waitForAppToMoveToDesktop(wmHelper: WindowManagerStateHelper) {
@@ -65,42 +78,117 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
fun enterDesktopWithDrag(
wmHelper: WindowManagerStateHelper,
device: UiDevice,
+ motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH)
) {
innerHelper.launchViaIntent(wmHelper)
- dragToDesktop(wmHelper, device)
+ dragToDesktop(
+ wmHelper = wmHelper,
+ device = device,
+ motionEventHelper = motionEventHelper
+ )
waitForAppToMoveToDesktop(wmHelper)
}
- private fun dragToDesktop(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ private fun dragToDesktop(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ motionEventHelper: MotionEventHelper
+ ) {
val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
val startX = windowRect.centerX()
// Start dragging a little under the top to prevent dragging the notification shade.
val startY = 10
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
+ val displayRect = getDisplayRect(wmHelper)
// The position we want to drag to
val endY = displayRect.centerY() / 2
// drag the window to move to desktop
- device.drag(startX, startY, startX, endY, 100)
+ if (motionEventHelper.inputMethod == TOUCH
+ && Flags.enableHoldToDragAppHandle()) {
+ // Touch requires hold-to-drag.
+ motionEventHelper.holdToDrag(startX, startY, startX, endY, steps = 100)
+ } else {
+ device.drag(startX, startY, startX, endY, 100)
+ }
+ }
+
+ private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
+ ?.children
+ ?.get(0)
+ ?: error("Unable to find resource $MAXIMIZE_BUTTON_VIEW\n")
}
/** Click maximise button on the app header for the given app. */
fun maximiseDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val caption = getCaptionForTheApp(wmHelper, device)
- val maximizeButton =
- caption
- ?.children
- ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
- ?.children
- ?.get(0)
- maximizeButton?.click()
+ val maximizeButton = getMaximizeButtonForTheApp(caption)
+ maximizeButton.click()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
+
+ private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) }
+ ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
+ }
+
+ fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val minimizeButton = getMinimizeButtonForTheApp(caption)
+ minimizeButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceDisappeared(innerHelper)
+ .waitForAndVerify()
+ }
+
+ /** Open maximize menu and click snap resize button on the app header for the given app. */
+ fun snapResizeDesktopApp(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ context: Context,
+ toLeft: Boolean
+ ) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val maximizeButton = getMaximizeButtonForTheApp(caption)
+ maximizeButton?.longClick()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
+ val maximizeMenu = getDesktopAppViewByRes(MAXIMIZE_MENU)
+
+ val snapResizeButton =
+ maximizeMenu
+ ?.wait(Until.findObject(By.res(SYSTEMUI_PACKAGE, buttonResId)), TIMEOUT.toMillis())
+ ?: error("Unable to find object with resource id $buttonResId")
+ snapResizeButton.click()
+
+ val displayRect = getDisplayRect(wmHelper)
+ val insets = getWindowInsets(
+ context, WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
+ )
+ displayRect.inset(insets)
+
+ val expectedWidth = displayRect.width() / 2
+ val expectedRect = Rect(displayRect).apply {
+ if (toLeft) right -= expectedWidth else left += expectedWidth
+ }
+
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withSurfaceVisibleRegion(this, Region(expectedRect))
+ .waitForAndVerify()
+ }
+
/** Click close button on the app header for the given app. */
fun closeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val caption = getCaptionForTheApp(wmHelper, device)
@@ -119,11 +207,10 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
): UiObject2? {
if (
wmHelper.getWindow(innerHelper)?.windowingMode !=
- WindowingMode.WINDOWING_MODE_FREEFORM.value
- )
- error("expected a freeform window with caption but window is not in freeform mode")
+ WindowingMode.WINDOWING_MODE_FREEFORM.value
+ ) error("expected a freeform window with caption but window is not in freeform mode")
val captions =
- device.wait(Until.findObjects(caption), TIMEOUT_MS)
+ device.wait(Until.findObjects(caption), TIMEOUT.toMillis())
?: error("Unable to find view $caption\n")
return captions.find {
@@ -147,7 +234,80 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
val endX = startX + horizontalChange
// drag the specified corner of the window to the end coordinate.
- device.drag(startX, startY, endX, endY, 100)
+ dragWindow(startX, startY, endX, endY, wmHelper, device)
+ }
+
+ /** Resize a desktop app from its edges. */
+ fun edgeResize(
+ wmHelper: WindowManagerStateHelper,
+ motionEvent: MotionEventHelper,
+ edge: Edges
+ ) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val (startX, startY) = getStartCoordinatesForEdgeResize(windowRect, edge)
+ val verticalChange = when (edge) {
+ Edges.LEFT -> 0
+ Edges.RIGHT -> 0
+ Edges.TOP -> -100
+ Edges.BOTTOM -> 100
+ }
+ val horizontalChange = when (edge) {
+ Edges.LEFT -> -100
+ Edges.RIGHT -> 100
+ Edges.TOP -> 0
+ Edges.BOTTOM -> 0
+ }
+
+ // The position we want to drag to
+ val endY = startY + verticalChange
+ val endX = startX + horizontalChange
+
+ val downTime = SystemClock.uptimeMillis()
+ motionEvent.actionDown(startX, startY, time = downTime)
+ motionEvent.actionMove(startX, startY, endX, endY, /* steps= */100, downTime = downTime)
+ motionEvent.actionUp(endX, endY, downTime = downTime)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ /** Drag a window from a source coordinate to a destination coordinate. */
+ fun dragWindow(
+ startX: Int, startY: Int,
+ endX: Int, endY: Int,
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice
+ ) {
+ device.drag(startX, startY, endX, endY, /* steps= */ 100)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ /** Drag a window to a snap resize region, found at the left and right edges of the screen. */
+ fun dragToSnapResizeRegion(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ isLeft: Boolean,
+ ) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ // Set start x-coordinate as center of app header.
+ val startX = windowRect.centerX()
+ val startY = windowRect.top
+
+ val displayRect = getDisplayRect(wmHelper)
+
+ val endX = if (isLeft) {
+ displayRect.left + SNAP_RESIZE_DRAG_INSET
+ } else {
+ displayRect.right - SNAP_RESIZE_DRAG_INSET
+ }
+ val endY = displayRect.centerY() / 2
+
+ // drag the window to snap resize
+ device.drag(startX, startY, endX, endY, /* steps= */ 100)
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
@@ -165,4 +325,101 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom)
}
}
+
+ private fun getStartCoordinatesForEdgeResize(
+ windowRect: Rect,
+ edge: Edges
+ ): Pair<Int, Int> {
+ return when (edge) {
+ Edges.LEFT -> Pair(windowRect.left, windowRect.bottom / 2)
+ Edges.RIGHT -> Pair(windowRect.right, windowRect.bottom / 2)
+ Edges.TOP -> Pair(windowRect.right / 2, windowRect.top)
+ Edges.BOTTOM -> Pair(windowRect.right / 2, windowRect.bottom)
+ }
+ }
+
+ /** Exit desktop mode by dragging the app handle to the top drag zone. */
+ fun exitDesktopWithDragToTopDragZone(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ ) {
+ dragAppWindowToTopDragZone(wmHelper, device)
+ waitForTransitionToFullscreen(wmHelper)
+ }
+
+ private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val displayRect = getDisplayRect(wmHelper)
+
+ val startX = windowRect.centerX()
+ val endX = displayRect.centerX()
+ val startY = windowRect.top
+ val endY = 0 // top of the screen
+
+ // drag the app window to top drag zone
+ device.drag(startX, startY, endX, endY, 100)
+ }
+
+ fun enterDesktopModeFromAppHandleMenu(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice
+ ) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val startX = windowRect.centerX()
+ // Click a little under the top to prevent opening the notification shade.
+ val startY = 10
+
+ // Click on the app handle coordinates.
+ device.click(startX, startY)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ val pill = getDesktopAppViewByRes(PILL_CONTAINER)
+ val desktopModeButton =
+ pill
+ ?.children
+ ?.find { it.resourceName.endsWith(DESKTOP_MODE_BUTTON) }
+
+ desktopModeButton?.click()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ private fun getDesktopAppViewByRes(viewResId: String): UiObject2? =
+ DeviceHelpers.waitForObj(By.res(SYSTEMUI_PACKAGE, viewResId), TIMEOUT)
+
+ private fun getDisplayRect(wmHelper: WindowManagerStateHelper): Rect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ ?: throw IllegalStateException("Default display is null")
+
+
+ /** Wait for transition to full screen to finish. */
+ private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(innerHelper)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+
+ private fun getWindowInsets(context: Context, typeMask: Int): Insets {
+ val wm: WindowManager = context.getSystemService(WindowManager::class.java)
+ ?: error("Unable to connect to WindowManager service")
+ val metricInsets = wm.currentWindowMetrics.windowInsets
+ return metricInsets.getInsetsIgnoringVisibility(typeMask)
+ }
+
+ private companion object {
+ val TIMEOUT: Duration = Duration.ofSeconds(3)
+ const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
+ const val CAPTION: String = "desktop_mode_caption"
+ const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view"
+ const val MAXIMIZE_MENU: String = "maximize_menu"
+ const val CLOSE_BUTTON: String = "close_window"
+ const val PILL_CONTAINER: String = "windowing_pill"
+ const val DESKTOP_MODE_BUTTON: String = "desktop_button"
+ const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
+ const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+ const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
+ val caption: BySelector
+ get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+ }
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index 634b6eedd7e6..fd13d14074d4 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -33,9 +33,9 @@ class LetterboxAppHelper
@JvmOverloads
constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL,
+ launcherName: String = ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
+ ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent()
) : StandardAppHelper(instr, launcherName, component) {
private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
@@ -128,6 +128,6 @@ constructor(
}
companion object {
- private const val BOUNDS_OFFSET: Int = 100
+ private const val BOUNDS_OFFSET: Int = 50
}
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt
new file mode 100644
index 000000000000..1fe60888fa52
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.view.ContentInfo.Source
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_STYLUS
+import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import android.view.MotionEvent.TOOL_TYPE_FINGER
+import android.view.MotionEvent.TOOL_TYPE_MOUSE
+import android.view.MotionEvent.TOOL_TYPE_STYLUS
+import android.view.MotionEvent.ToolType
+
+/**
+ * Helper class for injecting a custom motion event and performing some actions. This is used for
+ * instrumenting input injections like stylus, mouse and touchpad.
+ */
+class MotionEventHelper(
+ private val instr: Instrumentation,
+ val inputMethod: InputMethod
+) {
+ enum class InputMethod(@ToolType val toolType: Int, @Source val source: Int) {
+ STYLUS(TOOL_TYPE_STYLUS, SOURCE_STYLUS),
+ MOUSE(TOOL_TYPE_MOUSE, SOURCE_MOUSE),
+ TOUCHPAD(TOOL_TYPE_FINGER, SOURCE_MOUSE),
+ TOUCH(TOOL_TYPE_FINGER, SOURCE_TOUCHSCREEN)
+ }
+
+ fun actionDown(x: Int, y: Int, time: Long = SystemClock.uptimeMillis()) {
+ injectMotionEvent(ACTION_DOWN, x, y, downTime = time, eventTime = time)
+ }
+
+ fun actionUp(x: Int, y: Int, downTime: Long) {
+ injectMotionEvent(ACTION_UP, x, y, downTime = downTime)
+ }
+
+ fun actionMove(
+ startX: Int,
+ startY: Int,
+ endX: Int,
+ endY: Int,
+ steps: Int,
+ downTime: Long,
+ withMotionEventInjectDelay: Boolean = false
+ ) {
+ val incrementX = (endX - startX).toFloat() / (steps - 1)
+ val incrementY = (endY - startY).toFloat() / (steps - 1)
+
+ for (i in 0..steps) {
+ val time = SystemClock.uptimeMillis()
+ val x = startX + incrementX * i
+ val y = startY + incrementY * i
+
+ val moveEvent = getMotionEvent(downTime, time, ACTION_MOVE, x, y)
+ injectMotionEvent(moveEvent)
+ if (withMotionEventInjectDelay) {
+ SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS)
+ }
+ }
+ }
+
+ /**
+ * Drag from [startX], [startY] to [endX], [endY] with a "hold" period after touching down
+ * and before moving.
+ */
+ fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) {
+ val downTime = SystemClock.uptimeMillis()
+ actionDown(startX, startY, time = downTime)
+ SystemClock.sleep(100L) // Hold before dragging.
+ actionMove(
+ startX,
+ startY,
+ endX,
+ endY,
+ steps,
+ downTime,
+ withMotionEventInjectDelay = true
+ )
+ SystemClock.sleep(REGULAR_CLICK_LENGTH)
+ actionUp(startX, endX, downTime)
+ }
+
+ private fun injectMotionEvent(
+ action: Int,
+ x: Int,
+ y: Int,
+ downTime: Long = SystemClock.uptimeMillis(),
+ eventTime: Long = SystemClock.uptimeMillis()
+ ): MotionEvent {
+ val event = getMotionEvent(downTime, eventTime, action, x.toFloat(), y.toFloat())
+ injectMotionEvent(event)
+ return event
+ }
+
+ private fun injectMotionEvent(event: MotionEvent) {
+ instr.uiAutomation.injectInputEvent(event, true, false)
+ }
+
+ private fun getMotionEvent(
+ downTime: Long,
+ eventTime: Long,
+ action: Int,
+ x: Float,
+ y: Float,
+ ): MotionEvent {
+ val properties = MotionEvent.PointerProperties.createArray(1)
+ properties[0].toolType = inputMethod.toolType
+ properties[0].id = 1
+
+ val coords = MotionEvent.PointerCoords.createArray(1)
+ coords[0].x = x
+ coords[0].y = y
+ coords[0].pressure = 1f
+
+ val event =
+ MotionEvent.obtain(
+ downTime,
+ eventTime,
+ action,
+ /* pointerCount= */ 1,
+ properties,
+ coords,
+ /* metaState= */ 0,
+ /* buttonState= */ 0,
+ /* xPrecision = */ 1f,
+ /* yPrecision = */ 1f,
+ /* deviceId = */ 0,
+ /* edgeFlags = */ 0,
+ inputMethod.source,
+ /* flags = */ 0
+ )
+ event.displayId = 0
+ return event
+ }
+
+ companion object {
+ private const val MOTION_EVENT_INJECTION_DELAY_MILLIS = 5L
+ private const val REGULAR_CLICK_LENGTH = 100L
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 43fd57bf39aa..931e4f88aa8d 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -269,9 +269,23 @@ open class PipAppHelper(instrumentation: Instrumentation) :
/** Expand the PIP window back to full screen via intent and wait until the app is visible */
fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) = launchViaIntent(wmHelper)
- fun changeAspectRatio() {
+ fun changeAspectRatio(wmHelper: WindowManagerStateHelper) {
val intent = Intent("com.android.wm.shell.flicker.testapp.ASPECT_RATIO")
context.sendBroadcast(intent)
+ // Wait on WMHelper on size change upon aspect ratio change
+ val windowRect = getWindowRect(wmHelper)
+ wmHelper
+ .StateSyncBuilder()
+ .add("pipAspectRatioChanged") {
+ val pipAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val pipRegion = pipAppWindow.frameRegion
+ return@add pipRegion != Region(windowRect)
+ }
+ .waitForAndVerify()
}
fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
new file mode 100644
index 000000000000..69fde0168b14
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 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.flicker.helpers
+
+import android.app.Instrumentation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.helpers.SYSTEMUI_PACKAGE
+import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.toFlickerComponent
+import android.util.Log
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiObjectNotFoundException
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import java.util.regex.Pattern
+
+class StartMediaProjectionAppHelper
+@JvmOverloads
+constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.StartMediaProjectionActivity.LABEL,
+ component: ComponentNameMatcher =
+ ActivityOptions.StartMediaProjectionActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
+ private val packageManager = instr.context.packageManager
+
+ fun startEntireScreenMediaProjection(wmHelper: WindowManagerStateHelper) {
+ clickStartMediaProjectionButton()
+ chooseEntireScreenOption()
+ startScreenSharing()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ fun startSingleAppMediaProjection(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper
+ ) {
+ clickStartMediaProjectionButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetApp(targetApp.appName)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .waitForAndVerify()
+ }
+
+ private fun clickStartMediaProjectionButton() {
+ findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() }
+ }
+
+ private fun chooseEntireScreenOption() {
+ findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
+
+ val entireScreenString = getSysUiResourceString(ENTIRE_SCREEN_STRING_RES_NAME)
+ findObject(By.text(entireScreenString)).also { it.click() }
+ }
+
+ private fun selectTargetApp(targetAppName: String) {
+ // Scroll to to find target app to launch then click app icon it to start capture
+ val scrollable = UiScrollable(UiSelector().scrollable(true))
+ try {
+ scrollable.scrollForward()
+ if (!scrollable.scrollIntoView(UiSelector().text(targetAppName))) {
+ Log.e(TAG, "Didn't find target app when scrolling")
+ return
+ }
+ } catch (e: UiObjectNotFoundException) {
+ Log.d(TAG, "There was no scrolling (UI may not be scrollable")
+ }
+
+ findObject(By.text(targetAppName)).also { it.click() }
+ }
+
+ private fun chooseSingleAppOption() {
+ findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
+
+ val singleAppString = getSysUiResourceString(SINGLE_APP_STRING_RES_NAME)
+ findObject(By.text(singleAppString)).also { it.click() }
+ }
+
+ private fun startScreenSharing() {
+ findObject(By.res(ACCEPT_RESOURCE_ID)).also { it.click() }
+ }
+
+ private fun findObject(selector: BySelector): UiObject2 =
+ uiDevice.wait(Until.findObject(selector), TIMEOUT) ?: error("Can't find object $selector")
+
+ private fun getSysUiResourceString(resName: String): String =
+ with(packageManager.getResourcesForApplication(SYSTEMUI_PACKAGE)) {
+ getString(getIdentifier(resName, "string", SYSTEMUI_PACKAGE))
+ }
+
+ companion object {
+ const val TAG: String = "StartMediaProjectionAppHelper"
+ const val TIMEOUT: Long = 5000L
+ const val ACCEPT_RESOURCE_ID: String = "android:id/button1"
+ const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp"
+ val SCREEN_SHARE_OPTIONS_PATTERN: Pattern =
+ Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)")
+ const val ENTIRE_SCREEN_STRING_RES_NAME: String =
+ "screen_share_permission_dialog_option_entire_screen"
+ const val SINGLE_APP_STRING_RES_NAME: String =
+ "screen_share_permission_dialog_option_single_app"
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index e3b23b986c83..c55df8604362 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -19,6 +19,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_windowing_tools",
}
android_test {
@@ -46,6 +47,7 @@ android_test {
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
"wm-flicker-window-extensions",
+ "wm-shell-flicker-utils",
],
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 45260bddd355..9ce8e807f612 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -21,6 +21,15 @@
android:targetSdkVersion="35"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<application android:allowBackup="false"
android:supportsRtl="true">
@@ -106,6 +115,30 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".NonResizeableFixedAspectRatioPortraitActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:minAspectRatio="1.77"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableFixedAspectRatioPortraitActivity"
+ android:label="NonResizeableFixedAspectRatioPortraitActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".StartMediaProjectionActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.StartMediaProjectionActivity"
+ android:label="StartMediaProjectionActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".PortraitImmersiveActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitImmersiveActivity"
android:immersive="true"
@@ -123,6 +156,7 @@
<activity android:name=".LaunchTransparentActivity"
android:resizeableActivity="false"
android:screenOrientation="portrait"
+ android:minAspectRatio="1.77"
android:theme="@style/OptOutEdgeToEdge"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
android:label="LaunchTransparentActivity"
@@ -318,7 +352,8 @@
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
android:theme="@style/CutoutShortEdges"
android:label="SplitScreenPrimaryActivity"
- android:exported="true">
+ android:exported="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -329,7 +364,8 @@
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity"
android:theme="@style/CutoutShortEdges"
android:label="SplitScreenSecondaryActivity"
- android:exported="true">
+ android:exported="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -404,6 +440,11 @@
android:name="android.voice_interaction"
android:resource="@xml/interaction_service"/>
</service>
+ <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService"
+ android:foregroundServiceType="mediaProjection"
+ android:label="WMShellTestsMediaProjectionService"
+ android:enabled="true">
+ </service>
</application>
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 917aec1e809d..939ba81a47ea 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -21,8 +21,10 @@
android:background="@android:color/holo_orange_light">
<LinearLayout
+ android:id="@+id/launch_options_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingBottom="48dp"
android:orientation="vertical">
<Button
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 36cbf1a8fe84..365a0ea017f6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -27,15 +27,6 @@
where things are arranged differently and to circle back up to the top once we reach the
bottom. -->
- <!-- View used for testing sourceRectHint. -->
- <View
- android:id="@+id/source_rect"
- android:layout_width="320dp"
- android:layout_height="180dp"
- android:visibility="gone"
- android:background="@android:color/holo_green_light"
- />
-
<Button
android:id="@+id/enter_pip"
android:layout_width="wrap_content"
@@ -122,12 +113,11 @@
android:onClick="onRatioSelected"/>
</RadioGroup>
- <Button
+ <CheckBox
android:id="@+id/set_source_rect_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Set SourceRectHint"
- android:onClick="setSourceRectHint"/>
+ android:text="Set SourceRectHint"/>
<TextView
android:layout_width="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
new file mode 100644
index 000000000000..46f01e6c9752
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2024 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@android:color/holo_orange_light">
+
+ <Button
+ android:id="@+id/button_start_mp"
+ android:layout_width="500dp"
+ android:layout_height="500dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="Start Media Projection"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 80c1dd072df7..73625da9dfa5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -85,6 +85,18 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class NonResizeableFixedAspectRatioPortraitActivity {
+ public static final String LABEL = "NonResizeableFixedAspectRatioPortraitActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NonResizeableFixedAspectRatioPortraitActivity");
+ }
+
+ public static class StartMediaProjectionActivity {
+ public static final String LABEL = "StartMediaProjectionActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".StartMediaProjectionActivity");
+ }
+
public static class PortraitImmersiveActivity {
public static final String LABEL = "PortraitImmersiveActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java
index ef75d4ddcdcd..93254f7247b6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java
@@ -71,7 +71,7 @@ public class GameActivity extends Activity implements SurfaceHolder.Callback {
windowInsetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);
- // Hide both the status bar and the navigation bar.
- windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
+ // Hide status bar only to avoid flakiness on gesture quick switch cases.
+ windowInsetsController.hide(WindowInsetsCompat.Type.statusBars());
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java
new file mode 100644
index 000000000000..be38c259d00d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class NonResizeableFixedAspectRatioPortraitActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_non_resizeable);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 27eb5a06451a..13d7f7f0d521 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -43,10 +43,10 @@ import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Rational;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
@@ -70,7 +70,7 @@ public class PipActivity extends Activity {
*/
private static final String TITLE_STATE_PAUSED = "TestApp media is paused";
- private static final Rational RATIO_DEFAULT = null;
+ private static final Rational RATIO_DEFAULT = new Rational(16, 9);
private static final Rational RATIO_SQUARE = new Rational(1, 1);
private static final Rational RATIO_WIDE = new Rational(2, 1);
private static final Rational RATIO_TALL = new Rational(1, 2);
@@ -88,8 +88,7 @@ public class PipActivity extends Activity {
"com.android.wm.shell.flicker.testapp.ASPECT_RATIO";
private final PictureInPictureParams.Builder mPipParamsBuilder =
- new PictureInPictureParams.Builder()
- .setAspectRatio(RATIO_DEFAULT);
+ new PictureInPictureParams.Builder();
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
.setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
@@ -139,6 +138,9 @@ public class PipActivity extends Activity {
}
};
+ private Rational mAspectRatio = RATIO_DEFAULT;
+ private boolean mEnableSourceRectHint;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -156,6 +158,14 @@ public class PipActivity extends Activity {
findViewById(R.id.media_session_stop)
.setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED));
+ final CheckBox setSourceRectHintCheckBox = findViewById(R.id.set_source_rect_hint);
+ setSourceRectHintCheckBox.setOnCheckedChangeListener((v, isChecked) -> {
+ if (mEnableSourceRectHint != isChecked) {
+ mEnableSourceRectHint = isChecked;
+ updateSourceRectHint();
+ }
+ });
+
mMediaSession = new MediaSession(this, "WMShell_TestApp");
mMediaSession.setPlaybackState(mPlaybackStateBuilder.build());
mMediaSession.setCallback(new MediaSession.Callback() {
@@ -250,47 +260,64 @@ public class PipActivity extends Activity {
}
}
+ private void updateSourceRectHint() {
+ if (!mEnableSourceRectHint) return;
+ // Similar to PipUtils#getEnterPipWithOverlaySrcRectHint, crop the display bounds
+ // as source rect hint based on the current aspect ratio.
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ final Rect displayBounds = new Rect(0, 0,
+ displayMetrics.widthPixels, displayMetrics.heightPixels);
+ final Rect sourceRectHint = getEnterPipWithOverlaySrcRectHint(
+ displayBounds, mAspectRatio.floatValue());
+ mPipParamsBuilder
+ .setAspectRatio(mAspectRatio)
+ .setSourceRectHint(sourceRectHint);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ }
+
/**
- * Adds a temporary view used for testing sourceRectHint.
- *
+ * Crop a Rect matches the aspect ratio and pivots at the center point.
+ * This is a counterpart of {@link PipUtils#getEnterPipWithOverlaySrcRectHint}
*/
- public void setSourceRectHint(View v) {
- View rectView = findViewById(R.id.source_rect);
- if (rectView != null) {
- rectView.setVisibility(View.VISIBLE);
- rectView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Rect boundingRect = new Rect();
- rectView.getGlobalVisibleRect(boundingRect);
- mPipParamsBuilder.setSourceRectHint(boundingRect);
- setPictureInPictureParams(mPipParamsBuilder.build());
- rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- rectView.invalidate(); // changing the visibility, invalidating to redraw the view
+ private Rect getEnterPipWithOverlaySrcRectHint(Rect appBounds, float aspectRatio) {
+ final float appBoundsAspectRatio = appBounds.width() / (float) appBounds.height();
+ final int width, height;
+ int left = appBounds.left;
+ int top = appBounds.top;
+ if (appBoundsAspectRatio < aspectRatio) {
+ width = appBounds.width();
+ height = (int) (width / aspectRatio);
+ top = appBounds.top + (appBounds.height() - height) / 2;
+ } else {
+ height = appBounds.height();
+ width = (int) (height * aspectRatio);
+ left = appBounds.left + (appBounds.width() - width) / 2;
}
+ return new Rect(left, top, left + width, top + height);
}
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
- mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT);
+ mAspectRatio = RATIO_DEFAULT;
break;
case R.id.ratio_square:
- mPipParamsBuilder.setAspectRatio(RATIO_SQUARE);
+ mAspectRatio = RATIO_SQUARE;
break;
case R.id.ratio_wide:
- mPipParamsBuilder.setAspectRatio(RATIO_WIDE);
+ mAspectRatio = RATIO_WIDE;
break;
case R.id.ratio_tall:
- mPipParamsBuilder.setAspectRatio(RATIO_TALL);
+ mAspectRatio = RATIO_TALL;
break;
}
+ setPictureInPictureParams(mPipParamsBuilder.setAspectRatio(mAspectRatio).build());
+ if (mEnableSourceRectHint) {
+ updateSourceRectHint();
+ }
}
private void updateMediaSessionState(int newState) {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
new file mode 100644
index 000000000000..a24a48269d7c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 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.flicker.testapp;
+
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.EXTRA_MESSENGER;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_SERVICE_DESTROYED;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_START_FOREGROUND_DONE;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Messenger;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Button;
+
+import com.android.wm.shell.flicker.utils.MediaProjectionService;
+
+public class StartMediaProjectionActivity extends Activity {
+
+ private static final String TAG = "StartMediaProjectionActivity";
+ private MediaProjectionManager mService;
+ private ImageReader mImageReader;
+ private VirtualDisplay mVirtualDisplay;
+ private MediaProjection mMediaProjection;
+ private MediaProjection.Callback mMediaProjectionCallback = new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ super.onCapturedContentResize(width, height);
+ }
+
+ @Override
+ public void onCapturedContentVisibilityChanged(boolean isVisible) {
+ super.onCapturedContentVisibilityChanged(isVisible);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mService = getSystemService(MediaProjectionManager.class);
+ setContentView(R.layout.activity_start_media_projection);
+
+ Button startMediaProjectionButton = findViewById(R.id.button_start_mp);
+ startMediaProjectionButton.setOnClickListener(v ->
+ startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE));
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_CODE) {
+ throw new IllegalStateException("Unknown request code: " + requestCode);
+ }
+ if (resultCode != RESULT_OK) {
+ throw new IllegalStateException("User denied screen sharing permission");
+ }
+ Log.d(TAG, "onActivityResult");
+ startMediaProjectionService(resultCode, data);
+ }
+
+ private void startMediaProjectionService(int resultCode, Intent resultData) {
+ final Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(),
+ msg -> {
+ switch (msg.what) {
+ case MSG_START_FOREGROUND_DONE:
+ setupMediaProjection(resultCode, resultData);
+ return true;
+ case MSG_SERVICE_DESTROYED:
+ return true;
+ }
+ Log.e(TAG, "Unknown message from the FlickerMPService: " + msg.what);
+ return false;
+ }
+ ));
+
+ final Intent intent = new Intent()
+ .setComponent(new ComponentName(this, MediaProjectionService.class))
+ .putExtra(EXTRA_MESSENGER, messenger);
+ startForegroundService(intent);
+ }
+
+ private void setupMediaProjection(int resultCode, Intent resultData) {
+ mMediaProjection = mService.getMediaProjection(resultCode, resultData);
+ if (mMediaProjection == null) {
+ throw new IllegalStateException("cannot create new MediaProjection");
+ }
+
+ mMediaProjection.registerCallback(
+ mMediaProjectionCallback, new Handler(Looper.getMainLooper()));
+
+ Rect displayBounds = getWindowManager().getMaximumWindowMetrics().getBounds();
+ mImageReader = ImageReader.newInstance(
+ displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1);
+
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay(
+ "DanielDisplay",
+ displayBounds.width(),
+ displayBounds.height(),
+ DisplayMetrics.DENSITY_HIGH,
+ /* flags= */ 0,
+ mImageReader.getSurface(),
+ new VirtualDisplay.Callback() {
+ @Override
+ public void onStopped() {
+ if (mMediaProjection != null) {
+ if (mMediaProjectionCallback != null) {
+ mMediaProjection.unregisterCallback(mMediaProjectionCallback);
+ mMediaProjectionCallback = null;
+ }
+ mMediaProjection.stop();
+ mMediaProjection = null;
+ }
+ if (mImageReader != null) {
+ mImageReader = null;
+ }
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.getSurface().release();
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ }
+ },
+ new Handler(Looper.getMainLooper())
+ );
+ }
+
+}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 084f9bd6d682..168141bf6e7d 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -39,8 +39,10 @@ android_test {
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
+ "junit-params",
"kotlin-test",
- "mockito-target-minus-junit4",
+ "mockito-kotlin-nodeps",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"platform-screenshot-diff-core",
"services.core.unboosted",
@@ -48,6 +50,7 @@ android_test {
"testables",
"testng",
"truth",
+ "ui-trace-collector",
],
libs: [
"android.test.mock.stubs.system",
diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml
index a05d08ccceba..914adc40194d 100644
--- a/tests/Input/AndroidManifest.xml
+++ b/tests/Input/AndroidManifest.xml
@@ -32,6 +32,14 @@
android:process=":externalProcess">
</activity>
+ <activity android:name="com.android.test.input.CaptureEventActivity"
+ android:label="Capture events"
+ android:configChanges="touchscreen|uiMode|orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
+ android:enableOnBackInvokedCallback="false"
+ android:turnScreenOn="true"
+ android:exported="true">
+ </activity>
+
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.test.input"
diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml
index 8db37058af2b..bc9322fbd3dc 100644
--- a/tests/Input/AndroidTest.xml
+++ b/tests/Input/AndroidTest.xml
@@ -22,6 +22,10 @@
<option name="shell-timeout" value="660s" />
<option name="test-timeout" value="600s" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/>
+ <!-- DefaultUITraceListener args -->
+ <option name="instrumentation-arg" key="skip_test_success_metrics" value="true"/>
+ <option name="instrumentation-arg" key="per_class" value="true"/>
</test>
<object class="com.android.tradefed.testtype.suite.module.TestFailureModuleController"
type="module_controller">
@@ -31,7 +35,9 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="pull-pattern-keys" value="input_.*" />
<!-- Pull files created by tests, like the output of screenshot tests -->
- <option name="directory-keys" value="/storage/emulated/0/InputTests" />
+ <option name="directory-keys" value="/sdcard/Download/InputTests" />
+ <!-- Pull perfetto traces from DefaultUITraceListener -->
+ <option name="pull-pattern-keys" value="perfetto_file_path*" />
<option name="collect-on-run-ended-only" value="false" />
</metrics_collector>
</configuration>
diff --git a/tests/Input/assets/testPointerFillStyle.png b/tests/Input/assets/testPointerFillStyle.png
index b2354f8f4799..297244f9d6d1 100644
--- a/tests/Input/assets/testPointerFillStyle.png
+++ b/tests/Input/assets/testPointerFillStyle.png
Binary files differ
diff --git a/tests/Input/assets/testPointerStrokeStyle.png b/tests/Input/assets/testPointerStrokeStyle.png
new file mode 100644
index 000000000000..4ddde70b2f0a
--- /dev/null
+++ b/tests/Input/assets/testPointerStrokeStyle.png
Binary files differ
diff --git a/tests/Input/res/drawable/test_key_drawable.xml b/tests/Input/res/drawable/test_key_drawable.xml
new file mode 100644
index 000000000000..2addf8fcf0bd
--- /dev/null
+++ b/tests/Input/res/drawable/test_key_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="4dp" />
+ <solid android:color="#ffffffff"/>
+</shape> \ No newline at end of file
diff --git a/tests/Input/res/drawable/test_modifier_drawable.xml b/tests/Input/res/drawable/test_modifier_drawable.xml
new file mode 100644
index 000000000000..2addf8fcf0bd
--- /dev/null
+++ b/tests/Input/res/drawable/test_modifier_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="4dp" />
+ <solid android:color="#ffffffff"/>
+</shape> \ No newline at end of file
diff --git a/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu b/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu
new file mode 100644
index 000000000000..1a9112b97301
--- /dev/null
+++ b/tests/Input/res/raw/google_pixel_tablet_touchscreen.evemu
@@ -0,0 +1,150 @@
+# EVEMU 1.2
+# One finger swipe gesture on the Google Pixel Tablet touchscreen
+N: NVTCapacitiveTouchScreen
+I: 001c 0603 7806 0100
+P: 02 00 00 00 00 00 00 00
+B: 00 0b 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 80 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 04 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 01 00 00 00 00 00 00 00 00
+B: 02 00 00 00 00 00 00 00 00
+B: 03 00 00 00 00 00 80 f3 46
+B: 04 00 00 00 00 00 00 00 00
+B: 05 00 00 00 00 00 00 00 00
+B: 11 00 00 00 00 00 00 00 00
+B: 12 00 00 00 00 00 00 00 00
+A: 2f 0 9 0 0 0
+A: 30 0 2559 0 0 11
+A: 31 0 1599 0 0 11
+A: 34 -4096 4096 0 0 0
+A: 35 0 1599 0 0 11
+A: 36 0 2559 0 0 11
+A: 37 0 2 0 0 0
+A: 39 0 65535 0 0 0
+A: 3a 0 256 0 0 0
+A: 3e 0 8 0 0 0
+E: 0.000001 0001 014a 0001
+E: 0.000001 0003 0039 0003
+E: 0.000001 0003 0035 0810
+E: 0.000001 0003 0036 1650
+E: 0.000001 0003 0030 0082
+E: 0.000001 0003 0031 0077
+E: 0.000001 0003 003a 0215
+E: 0.000001 0003 0034 3218
+E: 0.000001 0000 0000 0000
+E: 0.008818 0003 0035 0825
+E: 0.008818 0003 0036 1645
+E: 0.008818 0003 0034 3217
+E: 0.008818 0000 0000 0000
+E: 0.016306 0003 0035 0841
+E: 0.016306 0003 0036 1639
+E: 0.016306 0003 0034 3102
+E: 0.016306 0000 0000 0000
+E: 0.025653 0003 0035 0862
+E: 0.025653 0003 0036 1630
+E: 0.025653 0003 0034 3092
+E: 0.025653 0000 0000 0000
+E: 0.032936 0003 0035 0883
+E: 0.032936 0003 0036 1619
+E: 0.032936 0003 0034 3030
+E: 0.032936 0000 0000 0000
+E: 0.042072 0003 0035 0905
+E: 0.042072 0003 0036 1604
+E: 0.042072 0003 0034 2848
+E: 0.042072 0000 0000 0000
+E: 0.049569 0003 0035 0924
+E: 0.049569 0003 0036 1591
+E: 0.049569 0003 0034 2830
+E: 0.049569 0000 0000 0000
+E: 0.058706 0003 0035 0942
+E: 0.058706 0003 0036 1573
+E: 0.058706 0000 0000 0000
+E: 0.066207 0003 0035 0954
+E: 0.066207 0003 0036 1557
+E: 0.066207 0003 0034 2790
+E: 0.066207 0000 0000 0000
+E: 0.075337 0003 0035 0966
+E: 0.075337 0003 0036 1535
+E: 0.075337 0000 0000 0000
+E: 0.082841 0003 0035 0973
+E: 0.082841 0003 0036 1511
+E: 0.082841 0003 0034 2788
+E: 0.082841 0000 0000 0000
+E: 0.091972 0003 0035 0971
+E: 0.091972 0003 0036 1480
+E: 0.091972 0003 0034 2770
+E: 0.091972 0000 0000 0000
+E: 0.099474 0003 0035 0961
+E: 0.099474 0003 0036 1445
+E: 0.099474 0003 0034 2644
+E: 0.099474 0000 0000 0000
+E: 0.108631 0003 0035 0937
+E: 0.108631 0003 0036 1400
+E: 0.108631 0003 0030 0083
+E: 0.108631 0003 0034 2461
+E: 0.108631 0000 0000 0000
+E: 0.116109 0003 0035 0909
+E: 0.116109 0003 0036 1361
+E: 0.116109 0003 0034 2278
+E: 0.116109 0000 0000 0000
+E: 0.125263 0003 0035 0865
+E: 0.125263 0003 0036 1311
+E: 0.125263 0003 0034 2096
+E: 0.125263 0000 0000 0000
+E: 0.132741 0003 0035 0820
+E: 0.132741 0003 0036 1261
+E: 0.132741 0003 0034 2083
+E: 0.132741 0000 0000 0000
+E: 0.141876 0003 0035 0755
+E: 0.141876 0003 0036 1193
+E: 0.141876 0003 003a 0216
+E: 0.141876 0003 0034 2266
+E: 0.141876 0000 0000 0000
+E: 0.149376 0003 0035 0691
+E: 0.149376 0003 0036 1124
+E: 0.149376 0003 0034 2448
+E: 0.149376 0000 0000 0000
+E: 0.158510 0003 0035 0609
+E: 0.158510 0003 0036 1033
+E: 0.158510 0003 0034 2631
+E: 0.158510 0000 0000 0000
+E: 0.166011 0003 0035 0543
+E: 0.166011 0003 0036 0957
+E: 0.166011 0003 0034 2813
+E: 0.166011 0000 0000 0000
+E: 0.175182 0003 0035 0471
+E: 0.175182 0003 0036 0864
+E: 0.175182 0003 0031 0076
+E: 0.175182 0003 0034 2996
+E: 0.175182 0000 0000 0000
+E: 0.182683 0003 0035 0417
+E: 0.182683 0003 0036 0792
+E: 0.182683 0003 003a 0214
+E: 0.182683 0003 0034 3178
+E: 0.182683 0000 0000 0000
+E: 0.191777 0003 0035 0361
+E: 0.191777 0003 0036 0719
+E: 0.191777 0003 0031 0075
+E: 0.191777 0003 003a 0213
+E: 0.191777 0003 0034 2996
+E: 0.191777 0000 0000 0000
+E: 0.199431 0003 0035 0271
+E: 0.199431 0003 0036 0603
+E: 0.199431 0003 0030 0060
+E: 0.199431 0003 0031 0029
+E: 0.199431 0003 003a 0060
+E: 0.199431 0003 0034 2813
+E: 0.199431 0000 0000 0000
+E: 0.207943 0003 003a 0000
+E: 0.207943 0003 0039 -001
+E: 0.207943 0001 014a 0000
+E: 0.207943 0000 0000 0000
diff --git a/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json b/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json
new file mode 100644
index 000000000000..df4f9fb4e1df
--- /dev/null
+++ b/tests/Input/res/raw/google_pixel_tablet_touchscreen_events.json
@@ -0,0 +1,34 @@
+[
+ {
+ "name": "One finger swipe",
+ "source": "TOUCHSCREEN",
+ "events": [
+ {"action":"DOWN","axes":{"AXIS_X":810,"AXIS_Y":1650,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.234087586402893},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":825,"AXIS_Y":1645,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.2337040901184082},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":841,"AXIS_Y":1639,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1896021366119385},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":862,"AXIS_Y":1630,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1857671737670898},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":883,"AXIS_Y":1619,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.1619905233383179},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":905,"AXIS_Y":1604,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0921943187713623},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":924,"AXIS_Y":1591,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0852913856506348},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":942,"AXIS_Y":1573,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0852913856506348},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":954,"AXIS_Y":1557,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0699516534805298},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":966,"AXIS_Y":1535,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0699516534805298},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":973,"AXIS_Y":1511,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.06918466091156},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":971,"AXIS_Y":1480,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0622817277908325},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":961,"AXIS_Y":1445,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":82,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":82,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0139613151550293},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":937,"AXIS_Y":1400,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.9437817335128784},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":909,"AXIS_Y":1361,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.8736020922660828},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":865,"AXIS_Y":1311,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.803805947303772},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":820,"AXIS_Y":1261,"AXIS_PRESSURE":0.83984375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.7988204956054688},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":755,"AXIS_Y":1193,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.8690001368522644},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":691,"AXIS_Y":1124,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":0.9387962818145752},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":609,"AXIS_Y":1033,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.008975863456726},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":543,"AXIS_Y":957,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03126221150159836,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":77,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":77,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":471,"AXIS_Y":864,"AXIS_PRESSURE":0.84375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":76,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":76,"AXIS_ORIENTATION":1.1489516496658325},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":417,"AXIS_Y":792,"AXIS_PRESSURE":0.8359375,"AXIS_SIZE":0.03106682375073433,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":76,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":76,"AXIS_ORIENTATION":1.2187477350234985},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":361,"AXIS_Y":719,"AXIS_PRESSURE":0.83203125,"AXIS_SIZE":0.03087143413722515,"AXIS_TOUCH_MAJOR":83,"AXIS_TOUCH_MINOR":75,"AXIS_TOOL_MAJOR":83,"AXIS_TOOL_MINOR":75,"AXIS_ORIENTATION":1.1489516496658325},"buttonState":[]},
+ {"action":"MOVE","axes":{"AXIS_X":271,"AXIS_Y":603,"AXIS_PRESSURE":0.234375,"AXIS_SIZE":0.017389604821801186,"AXIS_TOUCH_MAJOR":60,"AXIS_TOUCH_MINOR":29,"AXIS_TOOL_MAJOR":60,"AXIS_TOOL_MINOR":29,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]},
+ {"action":"UP","axes":{"AXIS_X":271,"AXIS_Y":603,"AXIS_PRESSURE":0.234375,"AXIS_SIZE":0.017389604821801186,"AXIS_TOUCH_MAJOR":60,"AXIS_TOUCH_MINOR":29,"AXIS_TOOL_MAJOR":60,"AXIS_TOOL_MINOR":29,"AXIS_ORIENTATION":1.0787720680236816},"buttonState":[]}
+ ]
+ }
+]
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
new file mode 100644
index 000000000000..ba3f1871cdec
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 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.
+ -->
+<bookmarks>
+ <!-- the key combinations for the following shortcuts must be in sync
+ with the key combinations sent by the test in KeyGestureControllerTests.java -->
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+</bookmarks> \ No newline at end of file
diff --git a/tests/Input/res/xml/keyboard_glyph_maps.xml b/tests/Input/res/xml/keyboard_glyph_maps.xml
new file mode 100644
index 000000000000..42561c1a9923
--- /dev/null
+++ b/tests/Input/res/xml/keyboard_glyph_maps.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 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.
+ -->
+<keyboard-glyph-maps xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <keyboard-glyph-map
+ androidprv:glyphMap="@xml/test_glyph_map"
+ androidprv:vendorId="0x1234"
+ androidprv:productId="0x3456" />
+ <keyboard-glyph-map
+ androidprv:glyphMap="@xml/test_glyph_map2"
+ androidprv:vendorId="0x1235"
+ androidprv:productId="0x3457" />
+</keyboard-glyph-maps> \ No newline at end of file
diff --git a/tests/Input/res/xml/test_glyph_map.xml b/tests/Input/res/xml/test_glyph_map.xml
new file mode 100644
index 000000000000..7a7c1accb7fd
--- /dev/null
+++ b/tests/Input/res/xml/test_glyph_map.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 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.
+ -->
+<keyboard-glyph-map xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <key-glyph
+ androidprv:keycode="KEYCODE_BACK"
+ androidprv:glyphDrawable="@drawable/test_key_drawable" />
+ <modifier-glyph
+ androidprv:modifier="META"
+ androidprv:glyphDrawable="@drawable/test_modifier_drawable" />
+ <function-row-key androidprv:keycode="KEYCODE_EMOJI_PICKER" />
+ <hardware-defined-shortcut
+ androidprv:keycode="KEYCODE_1"
+ androidprv:modifierState="FUNCTION"
+ androidprv:outKeycode="KEYCODE_BACK" />
+ <hardware-defined-shortcut
+ androidprv:keycode="KEYCODE_2"
+ androidprv:modifierState="FUNCTION|META"
+ androidprv:outKeycode="KEYCODE_HOME" />
+</keyboard-glyph-map> \ No newline at end of file
diff --git a/tests/Input/res/xml/test_glyph_map2.xml b/tests/Input/res/xml/test_glyph_map2.xml
new file mode 100644
index 000000000000..7a7c1accb7fd
--- /dev/null
+++ b/tests/Input/res/xml/test_glyph_map2.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2024 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.
+ -->
+<keyboard-glyph-map xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <key-glyph
+ androidprv:keycode="KEYCODE_BACK"
+ androidprv:glyphDrawable="@drawable/test_key_drawable" />
+ <modifier-glyph
+ androidprv:modifier="META"
+ androidprv:glyphDrawable="@drawable/test_modifier_drawable" />
+ <function-row-key androidprv:keycode="KEYCODE_EMOJI_PICKER" />
+ <hardware-defined-shortcut
+ androidprv:keycode="KEYCODE_1"
+ androidprv:modifierState="FUNCTION"
+ androidprv:outKeycode="KEYCODE_BACK" />
+ <hardware-defined-shortcut
+ androidprv:keycode="KEYCODE_2"
+ androidprv:modifierState="FUNCTION|META"
+ androidprv:outKeycode="KEYCODE_HOME" />
+</keyboard-glyph-map> \ No newline at end of file
diff --git a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
index 90dff47ab706..a1e165551b5b 100644
--- a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt
@@ -24,17 +24,16 @@ import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import androidx.test.core.app.ApplicationProvider
import com.android.server.testutils.any
+import com.android.test.input.MockInputManagerRule
import java.util.concurrent.Executor
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.fail
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doAnswer
@@ -61,9 +60,8 @@ class InputDeviceBatteryListenerTest {
private lateinit var context: Context
private lateinit var inputManager: InputManager
- @Mock
- private lateinit var iInputManagerMock: IInputManager
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
@Before
fun setUp() {
@@ -72,7 +70,6 @@ class InputDeviceBatteryListenerTest {
executor = HandlerExecutor(Handler(testLooper.looper))
registeredListener = null
monitoredDevices.clear()
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
inputManager = InputManager(context)
`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
@@ -92,7 +89,7 @@ class InputDeviceBatteryListenerTest {
monitoredDevices.add(deviceId)
registeredListener = listener
null
- }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any())
+ }.`when`(inputManagerRule.mock).registerBatteryListener(anyInt(), any())
// Handle battery listener being unregistered.
doAnswer {
@@ -108,14 +105,7 @@ class InputDeviceBatteryListenerTest {
if (monitoredDevices.isEmpty()) {
registeredListener = null
}
- }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any())
- }
-
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
+ }.`when`(inputManagerRule.mock).unregisterBatteryListener(anyInt(), any())
}
private fun notifyBatteryStateChanged(
diff --git a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
index 080186e4a2c1..3fc9ce12e718 100644
--- a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
+++ b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -45,15 +45,14 @@ import android.view.InputDevice;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.test.input.MockInputManagerRule;
+
import org.junit.After;
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.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.Arrays;
@@ -73,23 +72,22 @@ public class InputDeviceLightsManagerTest {
private static final int DEVICE_ID = 1000;
private static final int PLAYER_ID = 3;
- @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final MockInputManagerRule mInputManagerRule = new MockInputManagerRule();
private InputManager mInputManager;
- @Mock private IInputManager mIInputManagerMock;
private InputManagerGlobal.TestSession mInputManagerGlobalSession;
@Before
public void setUp() throws Exception {
final Context context = spy(
new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()));
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+ when(mInputManagerRule.getMock().getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
- when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+ when(mInputManagerRule.getMock().getInputDevice(eq(DEVICE_ID))).thenReturn(
createInputDevice(DEVICE_ID));
- mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mInputManager = new InputManager(context);
when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
@@ -102,7 +100,7 @@ public class InputDeviceLightsManagerTest {
lightStatesById.put(lightIds[i], lightStates[i]);
}
return null;
- }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID),
+ }).when(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID),
any(int[].class), any(LightState[].class), any(IBinder.class));
doAnswer(invocation -> {
@@ -111,7 +109,7 @@ public class InputDeviceLightsManagerTest {
return lightStatesById.get(lightId);
}
return new LightState(0);
- }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt());
+ }).when(mInputManagerRule.getMock()).getLightState(eq(DEVICE_ID), anyInt());
}
@After
@@ -130,7 +128,7 @@ public class InputDeviceLightsManagerTest {
private void mockLights(Light[] lights) throws Exception {
// Mock the Lights returned form InputManagerService
- when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn(
+ when(mInputManagerRule.getMock().getLights(eq(DEVICE_ID))).thenReturn(
new ArrayList(Arrays.asList(lights)));
}
@@ -151,7 +149,7 @@ public class InputDeviceLightsManagerTest {
LightsManager lightsManager = device.getLightsManager();
List<Light> lights = lightsManager.getLights();
- verify(mIInputManagerMock).getLights(eq(DEVICE_ID));
+ verify(mInputManagerRule.getMock()).getLights(eq(DEVICE_ID));
assertEquals(lights, Arrays.asList(mockedLights));
}
@@ -185,9 +183,9 @@ public class InputDeviceLightsManagerTest {
.build());
IBinder token = session.getToken();
- verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
+ verify(mInputManagerRule.getMock()).openLightSession(eq(DEVICE_ID),
any(String.class), eq(token));
- verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}),
+ verify(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}),
eq(states), eq(token));
// Then all 3 should turn on.
@@ -204,7 +202,7 @@ public class InputDeviceLightsManagerTest {
// close session
session.close();
- verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
+ verify(mInputManagerRule.getMock()).closeLightSession(eq(DEVICE_ID), eq(token));
}
@Test
@@ -232,9 +230,9 @@ public class InputDeviceLightsManagerTest {
.build());
IBinder token = session.getToken();
- verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID),
+ verify(mInputManagerRule.getMock()).openLightSession(eq(DEVICE_ID),
any(String.class), eq(token));
- verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}),
+ verify(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), eq(new int[]{1}),
eq(states), eq(token));
// Verify the light state
@@ -245,7 +243,7 @@ public class InputDeviceLightsManagerTest {
// close session
session.close();
- verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token));
+ verify(mInputManagerRule.getMock()).closeLightSession(eq(DEVICE_ID), eq(token));
}
@Test
diff --git a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
index 0e3c200699d2..3057f5ddb540 100644
--- a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -41,16 +41,13 @@ import android.view.InputDevice;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.annotations.GuardedBy;
+import com.android.test.input.MockInputManagerRule;
-import org.junit.After;
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.MockitoJUnitRunner;
-import org.mockito.junit.MockitoRule;
import java.util.List;
import java.util.concurrent.BlockingQueue;
@@ -70,43 +67,34 @@ public class InputDeviceSensorManagerTest {
private static final int DEVICE_ID = 1000;
- @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final MockInputManagerRule mInputManagerRule = new MockInputManagerRule();
private InputManager mInputManager;
private IInputSensorEventListener mIInputSensorEventListener;
private final Object mLock = new Object();
- @Mock private IInputManager mIInputManagerMock;
- private InputManagerGlobal.TestSession mInputManagerGlobalSession;
-
@Before
public void setUp() throws Exception {
final Context context = spy(
new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()));
- mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mInputManager = new InputManager(context);
when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager);
- when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+ when(mInputManagerRule.getMock().getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
- when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+ when(mInputManagerRule.getMock().getInputDevice(eq(DEVICE_ID))).thenReturn(
createInputDeviceWithSensor(DEVICE_ID));
- when(mIInputManagerMock.getSensorList(eq(DEVICE_ID))).thenReturn(new InputSensorInfo[] {
- createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER),
- createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)});
+ when(mInputManagerRule.getMock().getSensorList(eq(DEVICE_ID))).thenReturn(
+ new InputSensorInfo[]{
+ createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER),
+ createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)});
- when(mIInputManagerMock.enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt()))
+ when(mInputManagerRule.getMock().enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt()))
.thenReturn(true);
- when(mIInputManagerMock.registerSensorListener(any())).thenReturn(true);
- }
-
- @After
- public void tearDown() {
- if (mInputManagerGlobalSession != null) {
- mInputManagerGlobalSession.close();
- }
+ when(mInputManagerRule.getMock().registerSensorListener(any())).thenReturn(true);
}
private class InputTestSensorEventListener implements SensorEventListener {
@@ -175,13 +163,13 @@ public class InputDeviceSensorManagerTest {
SensorManager sensorManager = device.getSensorManager();
List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
- verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+ verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID));
assertEquals(1, accelList.size());
assertEquals(DEVICE_ID, accelList.get(0).getId());
assertEquals(Sensor.TYPE_ACCELEROMETER, accelList.get(0).getType());
List<Sensor> gyroList = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE);
- verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+ verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID));
assertEquals(1, gyroList.size());
assertEquals(DEVICE_ID, gyroList.get(0).getId());
assertEquals(Sensor.TYPE_GYROSCOPE, gyroList.get(0).getType());
@@ -197,11 +185,11 @@ public class InputDeviceSensorManagerTest {
List<Sensor> gameRotationList = sensorManager.getSensorList(
Sensor.TYPE_GAME_ROTATION_VECTOR);
- verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+ verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID));
assertEquals(0, gameRotationList.size());
List<Sensor> gravityList = sensorManager.getSensorList(Sensor.TYPE_GRAVITY);
- verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID));
+ verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID));
assertEquals(0, gravityList.size());
}
@@ -218,13 +206,13 @@ public class InputDeviceSensorManagerTest {
mIInputSensorEventListener = invocation.getArgument(0);
assertNotNull(mIInputSensorEventListener);
return true;
- }).when(mIInputManagerMock).registerSensorListener(any());
+ }).when(mInputManagerRule.getMock()).registerSensorListener(any());
InputTestSensorEventListener listener = new InputTestSensorEventListener();
assertTrue(sensorManager.registerListener(listener, sensor,
SensorManager.SENSOR_DELAY_NORMAL));
- verify(mIInputManagerMock).registerSensorListener(any());
- verify(mIInputManagerMock).enableSensor(eq(DEVICE_ID), eq(sensor.getType()),
+ verify(mInputManagerRule.getMock()).registerSensorListener(any());
+ verify(mInputManagerRule.getMock()).enableSensor(eq(DEVICE_ID), eq(sensor.getType()),
anyInt(), anyInt());
float[] values = new float[] {0.12f, 9.8f, 0.2f};
@@ -240,7 +228,7 @@ public class InputDeviceSensorManagerTest {
}
sensorManager.unregisterListener(listener);
- verify(mIInputManagerMock).disableSensor(eq(DEVICE_ID), eq(sensor.getType()));
+ verify(mInputManagerRule.getMock()).disableSensor(eq(DEVICE_ID), eq(sensor.getType()));
}
}
diff --git a/tests/Input/src/android/hardware/input/InputManagerTest.kt b/tests/Input/src/android/hardware/input/InputManagerTest.kt
index 152dde94f006..4c6bb849155c 100644
--- a/tests/Input/src/android/hardware/input/InputManagerTest.kt
+++ b/tests/Input/src/android/hardware/input/InputManagerTest.kt
@@ -23,18 +23,16 @@ import android.view.Display
import android.view.DisplayInfo
import android.view.InputDevice
import androidx.test.core.app.ApplicationProvider
-import org.junit.After
-import org.junit.Assert.assertNotNull
+import com.android.test.input.MockInputManagerRule
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.eq
import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnitRunner
/**
@@ -54,35 +52,23 @@ class InputManagerTest {
}
@get:Rule
- val rule = MockitoJUnit.rule()!!
+ val inputManagerRule = MockInputManagerRule()
private lateinit var devicesChangedListener: IInputDevicesChangedListener
private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>()
private lateinit var context: Context
private lateinit var inputManager: InputManager
- @Mock
- private lateinit var iInputManager: IInputManager
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
-
@Before
fun setUp() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
- `when`(iInputManager.inputDeviceIds).then {
+ `when`(inputManagerRule.mock.inputDeviceIds).then {
deviceGenerationMap.keys.toIntArray()
}
}
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
- }
-
private fun notifyDeviceChanged(
deviceId: Int,
associatedDisplayId: Int,
@@ -92,7 +78,7 @@ class InputManagerTest {
?: throw IllegalArgumentException("Device $deviceId was never added!")
deviceGenerationMap[deviceId] = generation
- `when`(iInputManager.getInputDevice(deviceId))
+ `when`(inputManagerRule.mock.getInputDevice(deviceId))
.thenReturn(createInputDevice(deviceId, associatedDisplayId, usiVersion, generation))
val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
if (::devicesChangedListener.isInitialized) {
@@ -125,7 +111,7 @@ class InputManagerTest {
fun testUsiVersionFallBackToDisplayConfig() {
addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null)
- `when`(iInputManager.getHostUsiVersionFromDisplayConfig(eq(42)))
+ `when`(inputManagerRule.mock.getHostUsiVersionFromDisplayConfig(eq(42)))
.thenReturn(HostUsiVersion(9, 8))
val usiVersion = inputManager.getHostUsiVersion(createDisplay(42))
assertEquals(HostUsiVersion(9, 8), usiVersion)
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
new file mode 100644
index 000000000000..e99c81493394
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2024 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 android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.IBinder
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import com.android.test.input.MockInputManagerRule
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyGestureEventHandler].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureEventHandlerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyGestureEventHandlerTest {
+
+ companion object {
+ const val DEVICE_ID = 1
+ val HOME_GESTURE_EVENT = KeyGestureEvent.Builder()
+ .setDeviceId(DEVICE_ID)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_H))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON)
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ val BACK_GESTURE_EVENT = KeyGestureEvent.Builder()
+ .setDeviceId(DEVICE_ID)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_DEL))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON)
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build()
+ }
+
+ @get:Rule
+ val rule = SetFlagsRule()
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
+
+ private var registeredListener: IKeyGestureHandler? = null
+ private lateinit var context: Context
+ private lateinit var inputManager: InputManager
+
+ @Before
+ fun setUp() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ inputManager = InputManager(context)
+ `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+
+ // Handle key gesture handler registration.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyGestureHandler
+ if (registeredListener != null &&
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ // There can only be one registered key gesture handler per process.
+ fail("Trying to register a new listener when one already exists")
+ }
+ registeredListener = listener
+ null
+ }.`when`(inputManagerRule.mock).registerKeyGestureHandler(any())
+
+ // Handle key gesture handler being unregistered.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyGestureHandler
+ if (registeredListener == null ||
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ fail("Trying to unregister a listener that is not registered")
+ }
+ registeredListener = null
+ null
+ }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(any())
+ }
+
+ private fun handleKeyGestureEvent(event: KeyGestureEvent) {
+ val eventToSend = AidlKeyGestureEvent()
+ eventToSend.deviceId = event.deviceId
+ eventToSend.keycodes = event.keycodes
+ eventToSend.modifierState = event.modifierState
+ eventToSend.gestureType = event.keyGestureType
+ eventToSend.action = event.action
+ eventToSend.displayId = event.displayId
+ eventToSend.flags = event.flags
+ registeredListener!!.handleKeyGesture(eventToSend, null)
+ }
+
+ @Test
+ fun testHandlerHasCorrectGestureNotified() {
+ var callbackCount = 0
+
+ // Add a key gesture event listener
+ inputManager.registerKeyGestureEventHandler(KeyGestureHandler { event, _ ->
+ assertEquals(HOME_GESTURE_EVENT, event)
+ callbackCount++
+ true
+ })
+
+ // Request handling for key gesture event will notify the handler.
+ handleKeyGestureEvent(HOME_GESTURE_EVENT)
+ assertEquals(1, callbackCount)
+ }
+
+ @Test
+ fun testAddingHandlersRegistersInternalCallbackHandler() {
+ // Set up two callbacks.
+ val callback1 = KeyGestureHandler { _, _ -> false }
+ val callback2 = KeyGestureHandler { _, _ -> false }
+
+ assertNull(registeredListener)
+
+ // Adding the handler should register the callback with InputManagerService.
+ inputManager.registerKeyGestureEventHandler(callback1)
+ assertNotNull(registeredListener)
+
+ // Adding another handler should not register new internal listener.
+ val currListener = registeredListener
+ inputManager.registerKeyGestureEventHandler(callback2)
+ assertEquals(currListener, registeredListener)
+ }
+
+ @Test
+ fun testRemovingHandlersUnregistersInternalCallbackHandler() {
+ // Set up two callbacks.
+ val callback1 = KeyGestureHandler { _, _ -> false }
+ val callback2 = KeyGestureHandler { _, _ -> false }
+
+ inputManager.registerKeyGestureEventHandler(callback1)
+ inputManager.registerKeyGestureEventHandler(callback2)
+
+ // Only removing all handlers should remove the internal callback
+ inputManager.unregisterKeyGestureEventHandler(callback1)
+ assertNotNull(registeredListener)
+ inputManager.unregisterKeyGestureEventHandler(callback2)
+ assertNull(registeredListener)
+ }
+
+ @Test
+ fun testMultipleHandlers() {
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ // Handler 1 captures all home gestures
+ val callback1 = KeyGestureHandler { event, _ ->
+ callbackCount1++
+ event.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ }
+ // Handler 2 captures all gestures
+ val callback2 = KeyGestureHandler { _, _ ->
+ callbackCount2++
+ true
+ }
+
+ // Add both key gesture event handlers
+ inputManager.registerKeyGestureEventHandler(callback1)
+ inputManager.registerKeyGestureEventHandler(callback2)
+
+ // Request handling for key gesture event, should notify callbacks in order. So, only the
+ // first handler should receive a callback since it captures the event.
+ handleKeyGestureEvent(HOME_GESTURE_EVENT)
+ assertEquals(1, callbackCount1)
+ assertEquals(0, callbackCount2)
+
+ // Second handler should receive the event since the first handler doesn't capture the event
+ handleKeyGestureEvent(BACK_GESTURE_EVENT)
+ assertEquals(2, callbackCount1)
+ assertEquals(1, callbackCount2)
+
+ inputManager.unregisterKeyGestureEventHandler(callback1)
+ // Request handling for key gesture event, should still trigger callback2 but not callback1.
+ handleKeyGestureEvent(HOME_GESTURE_EVENT)
+ assertEquals(2, callbackCount1)
+ assertEquals(2, callbackCount2)
+ }
+
+ inner class KeyGestureHandler(
+ private var handler: (event: KeyGestureEvent, token: IBinder?) -> Boolean
+ ) : InputManager.KeyGestureEventHandler {
+
+ override fun handleKeyGestureEvent(
+ event: KeyGestureEvent,
+ focusedToken: IBinder?
+ ): Boolean {
+ return handler(event, focusedToken)
+ }
+
+ override fun isKeyGestureSupported(gestureType: Int): Boolean {
+ return true
+ }
+ }
+}
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
new file mode 100644
index 000000000000..cf0bfcc4f6df
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2024 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 android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import com.android.test.input.MockInputManagerRule
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for [InputManager.KeyGestureEventListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureEventListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyGestureEventListenerTest {
+
+ companion object {
+ const val DEVICE_ID = 1
+ val HOME_GESTURE_EVENT = KeyGestureEvent.Builder()
+ .setDeviceId(DEVICE_ID)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_H))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON)
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ }
+
+ @get:Rule
+ val rule = SetFlagsRule()
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
+
+ private val testLooper = TestLooper()
+ private val executor = HandlerExecutor(Handler(testLooper.looper))
+ private var registeredListener: IKeyGestureEventListener? = null
+ private lateinit var context: Context
+ private lateinit var inputManager: InputManager
+
+ @Before
+ fun setUp() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ inputManager = InputManager(context)
+ `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+
+ // Handle key gesture event listener registration.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyGestureEventListener
+ if (registeredListener != null &&
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ // There can only be one registered key gesture event listener per process.
+ fail("Trying to register a new listener when one already exists")
+ }
+ registeredListener = listener
+ null
+ }.`when`(inputManagerRule.mock).registerKeyGestureEventListener(any())
+
+ // Handle key gesture event listener being unregistered.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyGestureEventListener
+ if (registeredListener == null ||
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ fail("Trying to unregister a listener that is not registered")
+ }
+ registeredListener = null
+ null
+ }.`when`(inputManagerRule.mock).unregisterKeyGestureEventListener(any())
+ }
+
+ private fun notifyKeyGestureEvent(event: KeyGestureEvent) {
+ val eventToSend = AidlKeyGestureEvent()
+ eventToSend.deviceId = event.deviceId
+ eventToSend.keycodes = event.keycodes
+ eventToSend.modifierState = event.modifierState
+ eventToSend.gestureType = event.keyGestureType
+ eventToSend.action = event.action
+ eventToSend.displayId = event.displayId
+ eventToSend.flags = event.flags
+ registeredListener!!.onKeyGestureEvent(eventToSend)
+ }
+
+ @Test
+ fun testListenerHasCorrectGestureNotified() {
+ var callbackCount = 0
+
+ // Add a key gesture event listener
+ inputManager.registerKeyGestureEventListener(executor) {
+ event: KeyGestureEvent ->
+ assertEquals(HOME_GESTURE_EVENT, event)
+ callbackCount++
+ }
+
+ // Notifying key gesture event will notify the listener.
+ notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount)
+ }
+
+ @Test
+ fun testAddingListenersRegistersInternalCallbackListener() {
+ // Set up two callbacks.
+ val callback1 = InputManager.KeyGestureEventListener { _ -> }
+ val callback2 = InputManager.KeyGestureEventListener { _ -> }
+
+ assertNull(registeredListener)
+
+ // Adding the listener should register the callback with InputManagerService.
+ inputManager.registerKeyGestureEventListener(executor, callback1)
+ assertNotNull(registeredListener)
+
+ // Adding another listener should not register new internal listener.
+ val currListener = registeredListener
+ inputManager.registerKeyGestureEventListener(executor, callback2)
+ assertEquals(currListener, registeredListener)
+ }
+
+ @Test
+ fun testRemovingListenersUnregistersInternalCallbackListener() {
+ // Set up two callbacks.
+ val callback1 = InputManager.KeyGestureEventListener { _ -> }
+ val callback2 = InputManager.KeyGestureEventListener { _ -> }
+
+ inputManager.registerKeyGestureEventListener(executor, callback1)
+ inputManager.registerKeyGestureEventListener(executor, callback2)
+
+ // Only removing all listeners should remove the internal callback
+ inputManager.unregisterKeyGestureEventListener(callback1)
+ assertNotNull(registeredListener)
+ inputManager.unregisterKeyGestureEventListener(callback2)
+ assertNull(registeredListener)
+ }
+
+ @Test
+ fun testMultipleListeners() {
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.KeyGestureEventListener { _ -> callbackCount1++ }
+ val callback2 = InputManager.KeyGestureEventListener { _ -> callbackCount2++ }
+
+ // Add both key gesture event listeners
+ inputManager.registerKeyGestureEventListener(executor, callback1)
+ inputManager.registerKeyGestureEventListener(executor, callback2)
+
+ // Notifying key gesture event, should notify both the callbacks.
+ notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+ testLooper.dispatchAll()
+ assertEquals(1, callbackCount1)
+ assertEquals(1, callbackCount2)
+
+ inputManager.unregisterKeyGestureEventListener(callback2)
+ // Notifying key gesture event, should still trigger callback1 but not
+ // callback2.
+ notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+ testLooper.dispatchAll()
+ assertEquals(2, callbackCount1)
+ assertEquals(1, callbackCount2)
+ }
+}
diff --git a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
index 23135b2550b0..d25dee1d402c 100644
--- a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt
@@ -24,22 +24,20 @@ import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import androidx.test.core.app.ApplicationProvider
import com.android.server.testutils.any
-import org.junit.After
+import com.android.test.input.MockInputManagerRule
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnitRunner
-import java.util.concurrent.Executor
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.fail
/**
* Tests for [InputManager.KeyboardBacklightListener].
@@ -50,23 +48,19 @@ import kotlin.test.fail
@Presubmit
@RunWith(MockitoJUnitRunner::class)
class KeyboardBacklightListenerTest {
+
@get:Rule
- val rule = MockitoJUnit.rule()!!
+ val inputManagerRule = MockInputManagerRule()
private lateinit var testLooper: TestLooper
private var registeredListener: IKeyboardBacklightListener? = null
private lateinit var executor: Executor
private lateinit var context: Context
private lateinit var inputManager: InputManager
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
-
- @Mock
- private lateinit var iInputManagerMock: IInputManager
@Before
fun setUp() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
testLooper = TestLooper()
executor = HandlerExecutor(Handler(testLooper.looper))
registeredListener = null
@@ -84,7 +78,7 @@ class KeyboardBacklightListenerTest {
}
registeredListener = listener
null
- }.`when`(iInputManagerMock).registerKeyboardBacklightListener(any())
+ }.`when`(inputManagerRule.mock).registerKeyboardBacklightListener(any())
// Handle keyboard backlight listener being unregistered.
doAnswer {
@@ -95,14 +89,7 @@ class KeyboardBacklightListenerTest {
}
registeredListener = null
null
- }.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any())
- }
-
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
+ }.`when`(inputManagerRule.mock).unregisterKeyboardBacklightListener(any())
}
private fun notifyKeyboardBacklightChanged(
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index bcd56ad0c669..1c2a0538e552 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -27,21 +27,20 @@ import android.platform.test.flag.junit.SetFlagsRule
import android.view.KeyEvent
import androidx.test.core.app.ApplicationProvider
import com.android.server.testutils.any
-import org.junit.After
+import com.android.test.input.MockInputManagerRule
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnitRunner
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
/**
* Tests for [InputManager.StickyModifierStateListener].
@@ -59,21 +58,18 @@ class StickyModifierStateListenerTest {
@get:Rule
val rule = SetFlagsRule()
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
private val testLooper = TestLooper()
private val executor = HandlerExecutor(Handler(testLooper.looper))
private var registeredListener: IStickyModifierStateListener? = null
private lateinit var context: Context
private lateinit var inputManager: InputManager
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
-
- @Mock
- private lateinit var iInputManagerMock: IInputManager
@Before
fun setUp() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
inputManager = InputManager(context)
`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
@@ -88,7 +84,7 @@ class StickyModifierStateListenerTest {
}
registeredListener = listener
null
- }.`when`(iInputManagerMock).registerStickyModifierStateListener(any())
+ }.`when`(inputManagerRule.mock).registerStickyModifierStateListener(any())
// Handle sticky modifier state listener being unregistered.
doAnswer {
@@ -99,14 +95,7 @@ class StickyModifierStateListenerTest {
}
registeredListener = null
null
- }.`when`(iInputManagerMock).unregisterStickyModifierStateListener(any())
- }
-
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
+ }.`when`(inputManagerRule.mock).unregisterStickyModifierStateListener(any())
}
private fun notifyStickyModifierStateChanged(modifierState: Int, lockedModifierState: Int) {
diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
index f2724e605553..044f11d6904c 100644
--- a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt
@@ -27,7 +27,6 @@ import android.hardware.input.HostUsiVersion
import android.hardware.input.IInputDeviceBatteryListener
import android.hardware.input.IInputDeviceBatteryState
import android.hardware.input.IInputDevicesChangedListener
-import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
import android.os.Binder
@@ -42,13 +41,13 @@ import com.android.server.input.BatteryController.BluetoothBatteryManager.Blueto
import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
import com.android.server.input.BatteryController.UEventBatteryListener
import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
+import com.android.test.input.MockInputManagerRule
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.IsEqual.equalTo
-import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -184,12 +183,12 @@ class BatteryControllerTests {
@get:Rule
val rule = MockitoJUnit.rule()!!
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
@Mock
private lateinit var native: NativeInputManagerService
@Mock
- private lateinit var iInputManager: IInputManager
- @Mock
private lateinit var uEventManager: UEventManager
@Mock
private lateinit var bluetoothBatteryManager: BluetoothBatteryManager
@@ -205,10 +204,9 @@ class BatteryControllerTests {
fun setup() {
context = TestableContext(ApplicationProvider.getApplicationContext())
testLooper = TestLooper()
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
context.addMockSystemService(InputManager::class.java, inputManager)
- `when`(iInputManager.inputDeviceIds).then {
+ `when`(inputManagerRule.mock.inputDeviceIds).then {
deviceGenerationMap.keys.toIntArray()
}
addInputDevice(DEVICE_ID)
@@ -218,18 +216,11 @@ class BatteryControllerTests {
bluetoothBatteryManager)
batteryController.systemRunning()
val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java)
- verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture())
+ verify(inputManagerRule.mock).registerInputDevicesChangedListener(listenerCaptor.capture())
devicesChangedListener = listenerCaptor.value
testLooper.dispatchAll()
}
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
- }
-
private fun notifyDeviceChanged(
deviceId: Int,
hasBattery: Boolean = true,
@@ -239,7 +230,7 @@ class BatteryControllerTests {
?: throw IllegalArgumentException("Device $deviceId was never added!")
deviceGenerationMap[deviceId] = generation
- `when`(iInputManager.getInputDevice(deviceId))
+ `when`(inputManagerRule.mock.getInputDevice(deviceId))
.thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation))
val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) }
if (::devicesChangedListener.isInitialized) {
@@ -657,9 +648,9 @@ class BatteryControllerTests {
@Test
fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() {
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
- `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
.thenReturn("11:22:33:44:55:66")
addInputDevice(BT_DEVICE_ID)
testLooper.dispatchNext()
@@ -686,7 +677,7 @@ class BatteryControllerTests {
batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID)
verify(bluetoothBatteryManager, never()).removeBatteryListener(any())
- `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
.thenReturn(null)
notifyDeviceChanged(SECOND_BT_DEVICE_ID)
testLooper.dispatchNext()
@@ -695,7 +686,7 @@ class BatteryControllerTests {
@Test
fun testNotifiesBluetoothBatteryChanges() {
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
`when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
addInputDevice(BT_DEVICE_ID)
@@ -716,7 +707,7 @@ class BatteryControllerTests {
@Test
fun testBluetoothBatteryIsPrioritized() {
`when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
`when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
`when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
@@ -745,7 +736,7 @@ class BatteryControllerTests {
@Test
fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() {
`when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device")
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
`when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
`when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98)
@@ -776,9 +767,9 @@ class BatteryControllerTests {
@Test
fun testRegisterBluetoothMetadataListenerForMonitoredBluetoothDevices() {
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
- `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
.thenReturn("11:22:33:44:55:66")
addInputDevice(BT_DEVICE_ID)
testLooper.dispatchNext()
@@ -811,7 +802,7 @@ class BatteryControllerTests {
verify(bluetoothBatteryManager)
.removeMetadataListener("AA:BB:CC:DD:EE:FF", metadataListener1.value)
- `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID))
.thenReturn(null)
notifyDeviceChanged(SECOND_BT_DEVICE_ID)
testLooper.dispatchNext()
@@ -821,7 +812,7 @@ class BatteryControllerTests {
@Test
fun testNotifiesBluetoothMetadataBatteryChanges() {
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
`when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF",
BluetoothDevice.METADATA_MAIN_BATTERY))
@@ -861,7 +852,7 @@ class BatteryControllerTests {
@Test
fun testBluetoothMetadataBatteryIsPrioritized() {
- `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
+ `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID))
.thenReturn("AA:BB:CC:DD:EE:FF")
`when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21)
`when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF",
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
new file mode 100644
index 000000000000..862886ce69d2
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2024 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.input
+
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGestureEvent
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests for custom keyboard glyph map configuration.
+ *
+ * Build/Install/Run:
+ * atest InputTests:CustomInputGestureManagerTests
+ */
+@Presubmit
+class InputGestureManagerTests {
+
+ companion object {
+ const val USER_ID = 1
+ }
+
+ private lateinit var inputGestureManager: InputGestureManager
+
+ @Before
+ fun setup() {
+ inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext())
+ }
+
+ @Test
+ fun addRemoveCustomGesture() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ val result = inputGestureManager.addCustomInputGesture(USER_ID, customGesture)
+ assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, result)
+ assertEquals(
+ listOf(customGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+
+ inputGestureManager.removeCustomInputGesture(USER_ID, customGesture)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+
+ @Test
+ fun removeNonExistentGesture() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ val result = inputGestureManager.removeCustomInputGesture(USER_ID, customGesture)
+ assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, result)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+
+ @Test
+ fun addAlreadyExistentGesture() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customGesture)
+ val customGesture2 = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build()
+ val result = inputGestureManager.addCustomInputGesture(USER_ID, customGesture2)
+ assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, result)
+ assertEquals(
+ listOf(customGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+
+ @Test
+ fun addRemoveAllExistentGestures() {
+ val customGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customGesture)
+ val customGesture2 = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customGesture2)
+
+ assertEquals(
+ listOf(customGesture, customGesture2),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+
+ inputGestureManager.removeAllCustomInputGestures(USER_ID)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID)
+ )
+ }
+} \ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 3c72498082e4..6eb00457a1a6 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -17,8 +17,12 @@
package com.android.server.input
+import android.Manifest
import android.content.Context
import android.content.ContextWrapper
+import android.content.PermissionChecker
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayViewport
import android.hardware.display.VirtualDisplay
@@ -27,20 +31,30 @@ import android.hardware.input.InputManagerGlobal
import android.os.InputEventInjectionSync
import android.os.SystemClock
import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
-import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.view.View.OnKeyListener
import android.view.InputDevice
+import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
+import android.view.WindowManager
import android.test.mock.MockContentResolver
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.policy.KeyInterceptionInfo
import com.android.internal.util.test.FakeSettingsProvider
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.server.LocalServices
+import com.android.server.wm.WindowManagerInternal
import com.google.common.truth.Truth.assertThat
-import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -49,15 +63,15 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing
/**
@@ -69,14 +83,29 @@ import org.mockito.stubbing.OngoingStubbing
@Presubmit
class InputManagerServiceTests {
+ companion object {
+ val ACTION_KEY_EVENTS = listOf(
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
+ KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
+ KeyEvent( /* downTime= */0, /* eventTime= */0, /* action= */0, /* code= */0,
+ /* repeat= */0, KeyEvent.META_META_ON
+ )
+ )
+ }
+
@get:Rule
- val mockitoRule = MockitoJUnit.rule()!!
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(LocalServices::class.java)
+ .mockStatic(PermissionChecker::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
@get:Rule
- val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
+ val setFlagsRule = SetFlagsRule()
@get:Rule
- val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()!!
+ val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
@Mock
private lateinit var native: NativeInputManagerService
@@ -85,8 +114,20 @@ class InputManagerServiceTests {
private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
@Mock
+ private lateinit var windowManagerInternal: WindowManagerInternal
+
+ @Mock
+ private lateinit var packageManagerInternal: PackageManagerInternal
+
+ @Mock
private lateinit var uEventManager: UEventManager
+ @Mock
+ private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
+
+ @Mock
+ private lateinit var kcm: KeyCharacterMap
+
private lateinit var service: InputManagerService
private lateinit var localService: InputManagerInternal
private lateinit var context: Context
@@ -113,11 +154,32 @@ class InputManagerServiceTests {
override fun registerLocalService(service: InputManagerInternal?) {
localService = service!!
}
+
+ override fun getKeyboardBacklightController(
+ nativeService: NativeInputManagerService?,
+ dataStore: PersistentDataStore?
+ ): InputManagerService.KeyboardBacklightControllerInterface {
+ return kbdController
+ }
})
inputManagerGlobalSession = InputManagerGlobal.createTestSession(service)
val inputManager = InputManager(context)
whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)
+ whenever(context.checkCallingOrSelfPermission(Manifest.permission.MANAGE_KEY_GESTURES))
+ .thenReturn(
+ PackageManager.PERMISSION_GRANTED
+ )
+
+ ExtendedMockito.doReturn(windowManagerInternal).`when` {
+ LocalServices.getService(eq(WindowManagerInternal::class.java))
+ }
+ ExtendedMockito.doReturn(packageManagerInternal).`when` {
+ LocalServices.getService(eq(PackageManagerInternal::class.java))
+ }
+ ExtendedMockito.doReturn(kcm).`when` {
+ KeyCharacterMap.load(anyInt())
+ }
assertTrue("Local service must be registered", this::localService.isInitialized)
service.setWindowManagerCallbacks(wmCallbacks)
@@ -151,14 +213,17 @@ class InputManagerServiceTests {
verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
verify(native).setTouchpadTapToClickEnabled(anyBoolean())
verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
+ verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+ verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
verify(native).setShowTouches(anyBoolean())
verify(native).setMotionClassifierEnabled(anyBoolean())
verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
verify(native).setStylusPointerIconEnabled(anyBoolean())
- // Called twice at boot, since there are individual callbacks to update the
- // key repeat timeout and the key repeat delay.
- verify(native, times(2)).setKeyRepeatConfiguration(anyInt(), anyInt())
+ // Called thrice at boot, since there are individual callbacks to update the
+ // key repeat timeout, the key repeat delay and whether key repeat enabled.
+ verify(native, times(3)).setKeyRepeatConfiguration(anyInt(), anyInt(),
+ anyBoolean())
}
@Test
@@ -194,7 +259,7 @@ class InputManagerServiceTests {
}
@Test
- fun testAddAndRemoveVirtualmKeyboardLayoutAssociation() {
+ fun testAddAndRemoveVirtualKeyboardLayoutAssociation() {
val inputPort = "input port"
val languageTag = "language"
val layoutType = "layoutType"
@@ -205,6 +270,48 @@ class InputManagerServiceTests {
verify(native, times(2)).changeKeyboardLayoutAssociation()
}
+ @Test
+ fun testActionKeyEventsForwardedToFocusedWindow_whenCorrectlyRequested() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */true,
+ /* hasPrivateFlag = */true
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ for (event in ACTION_KEY_EVENTS) {
+ assertEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+ }
+
+ @Test
+ fun testActionKeyEventsNotForwardedToFocusedWindow_whenNoPermissions() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */false,
+ /* hasPrivateFlag = */true
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ for (event in ACTION_KEY_EVENTS) {
+ assertNotEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+ }
+
+ @Test
+ fun testActionKeyEventsNotForwardedToFocusedWindow_whenNoPrivateFlag() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */true,
+ /* hasPrivateFlag = */false
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(-1)
+
+ for (event in ACTION_KEY_EVENTS) {
+ assertNotEquals(0, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+ }
+
private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
val displayManager: DisplayManager = context.getSystemService(
DisplayManager::class.java
@@ -372,6 +479,91 @@ class InputManagerServiceTests {
verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
}
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun handleKeyGestures_keyboardBacklight() {
+ service.systemRunning()
+
+ val backlightDownEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN)
+ service.interceptKeyBeforeDispatching(null, backlightDownEvent, /* policyFlags = */0)
+ verify(kbdController).decrementKeyboardBacklight(anyInt())
+
+ val backlightUpEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP)
+ service.interceptKeyBeforeDispatching(null, backlightUpEvent, /* policyFlags = */0)
+ verify(kbdController).incrementKeyboardBacklight(anyInt())
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun handleKeyGestures_toggleCapsLock() {
+ service.systemRunning()
+
+ val metaDownEvent = createKeyEvent(KeyEvent.KEYCODE_META_LEFT)
+ service.interceptKeyBeforeDispatching(null, metaDownEvent, /* policyFlags = */0)
+ val altDownEvent =
+ createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_DOWN)
+ service.interceptKeyBeforeDispatching(null, altDownEvent, /* policyFlags = */0)
+ val altUpEvent =
+ createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_UP)
+ service.interceptKeyBeforeDispatching(null, altUpEvent, /* policyFlags = */0)
+
+ verify(native).toggleCapsLock(anyInt())
+ }
+
+ fun overrideSendActionKeyEventsToFocusedWindow(
+ hasPermission: Boolean,
+ hasPrivateFlag: Boolean
+ ) {
+ ExtendedMockito.doReturn(
+ if (hasPermission) {
+ PermissionChecker.PERMISSION_GRANTED
+ } else {
+ PermissionChecker.PERMISSION_HARD_DENIED
+ }
+ ).`when` {
+ PermissionChecker.checkPermissionForDataDelivery(
+ any(),
+ eq(Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ any()
+ )
+ }
+
+ val info = KeyInterceptionInfo(
+ /* type = */0,
+ if (hasPrivateFlag) {
+ WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS
+ } else {
+ 0
+ },
+ "title",
+ /* uid = */0
+ )
+ whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
+ }
+
+ private fun createKeyEvent(
+ keycode: Int,
+ modifierState: Int = 0,
+ action: Int = KeyEvent.ACTION_DOWN
+ ): KeyEvent {
+ return KeyEvent(
+ /* downTime = */0,
+ /* eventTime = */0,
+ action,
+ keycode,
+ /* repeat = */0,
+ modifierState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD,
+ /* scancode = */0,
+ /* flags = */0,
+ InputDevice.SOURCE_KEYBOARD
+ )
+ }
}
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
index 11f46335f017..a236244546cb 100644
--- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java
+++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
@@ -133,6 +133,21 @@ public class InputShellCommandTest {
assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
}
+ @Test
+ public void testSwipeCommandEventFrequency() {
+ int[] durations = {100, 300, 500};
+ for (int durationMillis: durations) {
+ mInputEventInjector.mInjectedEvents.clear();
+ runCommand(String.format("swipe 200 800 200 200 %d", durationMillis));
+
+ // Add 2 events for ACTION_DOWN and ACTION_UP.
+ final int maxEventNum =
+ (int) Math.ceil(InputShellCommand.SWIPE_EVENT_HZ_DEFAULT
+ * (float) durationMillis / 1000) + 2;
+ assertThat(mInputEventInjector.mInjectedEvents.size()).isAtMost(maxEventNum);
+ }
+ }
+
private InputEvent getSingleInjectedInputEvent() {
assertThat(mInputEventInjector.mInjectedEvents).hasSize(1);
return mInputEventInjector.mInjectedEvents.get(0);
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
new file mode 100644
index 000000000000..1574d1b7ce6f
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -0,0 +1,1466 @@
+/*
+ * Copyright 2024 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.input
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.hardware.input.AidlKeyGestureEvent
+import android.hardware.input.AppLaunchData
+import android.hardware.input.IInputManager
+import android.hardware.input.IKeyGestureEventListener
+import android.hardware.input.IKeyGestureHandler
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager
+import android.hardware.input.InputManagerGlobal
+import android.hardware.input.KeyGestureEvent
+import android.os.IBinder
+import android.os.Process
+import android.os.SystemClock
+import android.os.SystemProperties
+import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.InputDevice
+import android.view.KeyCharacterMap
+import android.view.KeyEvent
+import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE
+import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.R
+import com.android.internal.annotations.Keep
+import com.android.internal.util.FrameworkStatsLog
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import junitparams.JUnitParamsRunner
+import junitparams.Parameters
+import org.junit.After
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+
+/**
+ * Tests for {@link KeyGestureController}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureControllerTests
+ */
+@Presubmit
+@RunWith(JUnitParamsRunner::class)
+class KeyGestureControllerTests {
+
+ companion object {
+ const val DEVICE_ID = 1
+ val HOME_GESTURE_COMPLETE_EVENT = KeyGestureEvent.Builder()
+ .setDeviceId(DEVICE_ID)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_H))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON)
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ .build()
+ val MODIFIER = mapOf(
+ KeyEvent.KEYCODE_CTRL_LEFT to (KeyEvent.META_CTRL_LEFT_ON or KeyEvent.META_CTRL_ON),
+ KeyEvent.KEYCODE_CTRL_RIGHT to (KeyEvent.META_CTRL_RIGHT_ON or KeyEvent.META_CTRL_ON),
+ KeyEvent.KEYCODE_ALT_LEFT to (KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON),
+ KeyEvent.KEYCODE_ALT_RIGHT to (KeyEvent.META_ALT_RIGHT_ON or KeyEvent.META_ALT_ON),
+ KeyEvent.KEYCODE_SHIFT_LEFT to (KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON),
+ KeyEvent.KEYCODE_SHIFT_RIGHT to (KeyEvent.META_SHIFT_RIGHT_ON or KeyEvent.META_SHIFT_ON),
+ KeyEvent.KEYCODE_META_LEFT to (KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON),
+ KeyEvent.KEYCODE_META_RIGHT to (KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON),
+ )
+ const val SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0
+ const val SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1
+ const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0
+ const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1
+ const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2
+ }
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(SystemProperties::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
+
+ @JvmField
+ @Rule
+ val rule = SetFlagsRule()
+
+ @Mock
+ private lateinit var iInputManager: IInputManager
+
+ @Mock
+ private lateinit var resources: Resources
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private var currentPid = 0
+ private lateinit var context: Context
+ private lateinit var keyGestureController: KeyGestureController
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+ private lateinit var testLooper: TestLooper
+ private var events = mutableListOf<KeyGestureEvent>()
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ setupInputDevices()
+ setupBehaviors()
+ testLooper = TestLooper()
+ currentPid = Process.myPid()
+ }
+
+ @After
+ fun teardown() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ }
+
+ private fun setupBehaviors() {
+ Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
+ Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
+ val testBookmarks: XmlResourceParser = context.resources.getXml(
+ com.android.test.input.R.xml.bookmarks
+ )
+ Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+ Mockito.`when`(context.resources).thenReturn(resources)
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ .thenReturn(true)
+ Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ .thenReturn(true)
+ Mockito.`when`(context.packageManager).thenReturn(packageManager)
+ }
+
+ private fun setupInputDevices() {
+ val correctIm = context.getSystemService(InputManager::class.java)!!
+ val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
+ val kcm = virtualDevice.keyCharacterMap!!
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+ val inputManager = InputManager(context)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+
+ val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build()
+ Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+ ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm)
+ }
+
+ private fun setupKeyGestureController() {
+ keyGestureController = KeyGestureController(context, testLooper.looper)
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+ }
+
+ private fun notifyHomeGestureCompleted() {
+ keyGestureController.notifyKeyGestureCompleted(
+ DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
+ KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ )
+ }
+
+ @Test
+ fun testKeyGestureEvent_registerUnregisterListener() {
+ setupKeyGestureController()
+ val listener = KeyGestureEventListener()
+
+ // Register key gesture event listener
+ keyGestureController.registerKeyGestureEventListener(listener, 0)
+ notifyHomeGestureCompleted()
+ testLooper.dispatchAll()
+ assertEquals(
+ "Listener should get callbacks on key gesture event completed",
+ 1,
+ events.size
+ )
+ assertEquals(
+ "Listener should get callback for key gesture complete event",
+ HOME_GESTURE_COMPLETE_EVENT,
+ events[0]
+ )
+
+ // Unregister listener
+ events.clear()
+ keyGestureController.unregisterKeyGestureEventListener(listener, 0)
+ notifyHomeGestureCompleted()
+ testLooper.dispatchAll()
+ assertEquals(
+ "Listener should not get callback after being unregistered",
+ 0,
+ events.size
+ )
+ }
+
+ @Test
+ fun testKeyGestureEvent_multipleGestureHandlers() {
+ setupKeyGestureController()
+
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ var selfCallback = 0
+ val externalHandler1 = KeyGestureHandler { _, _ ->
+ callbackCount1++
+ true
+ }
+ val externalHandler2 = KeyGestureHandler { _, _ ->
+ callbackCount2++
+ true
+ }
+ val selfHandler = KeyGestureHandler { _, _ ->
+ selfCallback++
+ false
+ }
+
+ // Register key gesture handler: External process (last in priority)
+ keyGestureController.registerKeyGestureHandler(externalHandler1, currentPid + 1)
+
+ // Register key gesture handler: External process (second in priority)
+ keyGestureController.registerKeyGestureHandler(externalHandler2, currentPid - 1)
+
+ // Register key gesture handler: Self process (first in priority)
+ keyGestureController.registerKeyGestureHandler(selfHandler, currentPid)
+
+ keyGestureController.handleKeyGesture(/* deviceId = */ 0, intArrayOf(KeyEvent.KEYCODE_HOME),
+ /* modifierState = */ 0, KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, /* displayId */ 0,
+ /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null
+ )
+
+ assertEquals(
+ "Self handler should get callbacks first",
+ 1,
+ selfCallback
+ )
+ assertEquals(
+ "Higher priority handler should get callbacks first",
+ 1,
+ callbackCount2
+ )
+ assertEquals(
+ "Lower priority handler should not get callbacks if already handled",
+ 0,
+ callbackCount1
+ )
+ }
+
+ class TestData(
+ val name: String,
+ val keys: IntArray,
+ val expectedKeyGestureType: Int,
+ val expectedKeys: IntArray,
+ val expectedModifierState: Int,
+ val expectedActions: IntArray,
+ val expectedAppLaunchData: AppLaunchData? = null,
+ ) {
+ override fun toString(): String = name
+ }
+
+ @Keep
+ private fun systemGesturesTestArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "META + A -> Launch Assistant",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_A),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+ intArrayOf(KeyEvent.KEYCODE_A),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + H -> Go Home",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H),
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ intArrayOf(KeyEvent.KEYCODE_H),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ENTER -> Go Home",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ENTER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ intArrayOf(KeyEvent.KEYCODE_ENTER),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + I -> Launch System Settings",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_I),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+ intArrayOf(KeyEvent.KEYCODE_I),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + L -> Lock",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_L),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+ intArrayOf(KeyEvent.KEYCODE_L),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + N -> Toggle Notification",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_N),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ intArrayOf(KeyEvent.KEYCODE_N),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + N -> Open Notes",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_N
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
+ intArrayOf(KeyEvent.KEYCODE_N),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + S -> Take Screenshot",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_S
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + DEL -> Back",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DEL),
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+ intArrayOf(KeyEvent.KEYCODE_DEL),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ESC -> Back",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ESCAPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + DPAD_LEFT -> Back",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DPAD_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + DPAD_UP -> Multi Window Navigation",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DPAD_UP
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_UP),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + DPAD_DOWN -> Desktop Mode",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DPAD_DOWN
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + DPAD_LEFT -> Splitscreen Navigation Left",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DPAD_LEFT
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + DPAD_RIGHT -> Splitscreen Navigation Right",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DPAD_RIGHT
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + DPAD_LEFT -> Change Splitscreen Focus Left",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_DPAD_LEFT
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + DPAD_RIGHT -> Change Splitscreen Focus Right",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_DPAD_RIGHT
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
+ intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + / -> Open Shortcut Helper",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+ intArrayOf(KeyEvent.KEYCODE_SLASH),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT -> Toggle Caps Lock",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + META -> Toggle Caps Lock",
+ intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + TAB -> Open Overview",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ intArrayOf(KeyEvent.KEYCODE_TAB),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + TAB -> Toggle Recent Apps Switcher",
+ intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+ intArrayOf(KeyEvent.KEYCODE_TAB),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "CTRL + SPACE -> Switch Language Forward",
+ intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_SPACE),
+ KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "CTRL + SHIFT + SPACE -> Switch Language Backward",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_SPACE
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_SPACE),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "CTRL + ALT + Z -> Accessibility Shortcut",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_Z
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ intArrayOf(KeyEvent.KEYCODE_Z),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + B -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + C -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + E -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_E),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "META + K -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_K),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "META + M -> Launch Default Maps",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+ ),
+ TestData(
+ "META + P -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_P),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "META + S -> Launch Default SMS",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+ ),
+ TestData(
+ "META + U -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_U),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
+ TestData(
+ "META + SHIFT + B -> Launch Default Browser",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_B
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + SHIFT + C -> Launch Default Contacts",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_C
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + SHIFT + J -> Launch Target Activity",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_J
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_J),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ ),
+ TestData(
+ "META + CTRL + DEL -> Trigger Bug Report",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DEL
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+ intArrayOf(KeyEvent.KEYCODE_DEL),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 3 -> Toggle Bounce Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_3
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_3),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 4 -> Toggle Mouse Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_4
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_4),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 5 -> Toggle Sticky Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_5
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_5),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 6 -> Toggle Slow Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_6
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_6),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + D -> Move a task to next display",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_D
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+ intArrayOf(KeyEvent.KEYCODE_D),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + [ -> Resizes a task to fit the left half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_LEFT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + ] -> Resizes a task to fit the right half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_RIGHT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + '=' -> Maximizes a task to fit the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + '-' -> Restores a task size to its previous bounds",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments")
+ @EnableFlags(
+ com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+ )
+ fun testKeyGestures(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments")
+ @EnableFlags(
+ com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+ )
+ fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) {
+ setupKeyGestureController()
+ // Need to re-init so that bookmarks are correctly blocklisted
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ test.expectedKeys[0],
+ test.expectedModifierState
+ )
+ )
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ assertEquals(
+ test.toString(),
+ InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE,
+ keyGestureController.addCustomInputGesture(0, builder.build().aidlData)
+ )
+ }
+
+ @Keep
+ private fun systemKeysTestArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "RECENT_APPS -> Show Overview",
+ intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "APP_SWITCH -> App Switch",
+ intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "BRIGHTNESS_UP -> Brightness Up",
+ intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
+ KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
+ intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "BRIGHTNESS_DOWN -> Brightness Down",
+ intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_DOWN),
+ KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+ intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_DOWN),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "KEYBOARD_BACKLIGHT_UP -> Keyboard Backlight Up",
+ intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP),
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+ intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "KEYBOARD_BACKLIGHT_DOWN -> Keyboard Backlight Down",
+ intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN),
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+ intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "KEYBOARD_BACKLIGHT_TOGGLE -> Keyboard Backlight Toggle",
+ intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+ intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALL_APPS -> Open App Drawer",
+ intArrayOf(KeyEvent.KEYCODE_ALL_APPS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
+ intArrayOf(KeyEvent.KEYCODE_ALL_APPS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "NOTIFICATION -> Toggle Notification Panel",
+ intArrayOf(KeyEvent.KEYCODE_NOTIFICATION),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ intArrayOf(KeyEvent.KEYCODE_NOTIFICATION),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "LANGUAGE_SWITCH -> Switch Language Forward",
+ intArrayOf(KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "SHIFT + LANGUAGE_SWITCH -> Switch Language Backward",
+ intArrayOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+ KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "SCREENSHOT -> Take Screenshot",
+ intArrayOf(KeyEvent.KEYCODE_SCREENSHOT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ intArrayOf(KeyEvent.KEYCODE_SCREENSHOT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META -> Open Apps Drawer",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "SYSRQ -> Take screenshot",
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ESC -> Close All Dialogs",
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "EXPLORER -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "ENVELOPE -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "CONTACTS -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "CALENDAR -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "MUSIC -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_MUSIC),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_MUSIC),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "CALCULATOR -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
+ )
+ }
+
+ @Test
+ @Parameters(method = "systemKeysTestArguments")
+ fun testSystemKeys(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
+ setupKeyGestureController()
+ val testKeys = intArrayOf(
+ KeyEvent.KEYCODE_RECENT_APPS,
+ KeyEvent.KEYCODE_APP_SWITCH,
+ KeyEvent.KEYCODE_BRIGHTNESS_UP,
+ KeyEvent.KEYCODE_BRIGHTNESS_DOWN,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE,
+ KeyEvent.KEYCODE_ALL_APPS,
+ KeyEvent.KEYCODE_NOTIFICATION,
+ KeyEvent.KEYCODE_SETTINGS,
+ KeyEvent.KEYCODE_LANGUAGE_SWITCH,
+ KeyEvent.KEYCODE_SCREENSHOT,
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_META_RIGHT,
+ KeyEvent.KEYCODE_ASSIST,
+ KeyEvent.KEYCODE_VOICE_ASSIST,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL,
+ )
+
+ val handler = KeyGestureHandler { _, _ -> false }
+ keyGestureController.registerKeyGestureHandler(handler, 0)
+
+ for (key in testKeys) {
+ sendKeys(intArrayOf(key), assertNotSentToApps = true)
+ }
+ }
+
+ @Test
+ fun testSearchKeyGestures_defaultSearch() {
+ Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
+ .thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH)
+ setupKeyGestureController()
+ testKeyGestureNotProduced(
+ "SEARCH -> Default Search",
+ intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ )
+ }
+
+ @Test
+ fun testSearchKeyGestures_searchActivity() {
+ Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
+ .thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY)
+ setupKeyGestureController()
+ testKeyGestureInternal(
+ TestData(
+ "SEARCH -> Launch Search Activity",
+ intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+ intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ fun testSettingKeyGestures_doNothing() {
+ Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
+ .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING)
+ setupKeyGestureController()
+ testKeyGestureNotProduced(
+ "SETTINGS -> Do Nothing",
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ )
+ }
+
+ @Test
+ fun testSettingKeyGestures_settingsActivity() {
+ Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
+ .thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY)
+ setupKeyGestureController()
+ testKeyGestureInternal(
+ TestData(
+ "SETTINGS -> Launch Settings Activity",
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ fun testSettingKeyGestures_notificationPanel() {
+ Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
+ .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL)
+ setupKeyGestureController()
+ testKeyGestureInternal(
+ TestData(
+ "SETTINGS -> Toggle Notification Panel",
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ fun testCapsLockPressNotified() {
+ setupKeyGestureController()
+ val listener = KeyGestureEventListener()
+
+ keyGestureController.registerKeyGestureEventListener(listener, 0)
+ sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
+ testLooper.dispatchAll()
+ assertEquals(
+ "Listener should get callbacks on key gesture event completed",
+ 1,
+ events.size
+ )
+ assertEquals(
+ "Listener should get callback for Toggle Caps Lock key gesture complete event",
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ events[0].keyGestureType
+ )
+ }
+
+ @Keep
+ private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "VOLUME_DOWN + POWER -> Screenshot Chord",
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "POWER + STEM_PRIMARY -> Screenshot Chord",
+ intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "VOLUME_DOWN + VOLUME_UP -> Accessibility Chord",
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "BACK + DPAD_DOWN -> TV Accessibility Chord",
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "BACK + DPAD_CENTER -> TV Trigger Bug Report",
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
+ intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ )
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments_forKeyCombinations")
+ @EnableFlags(
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
+ com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES
+ )
+ fun testKeyCombinationGestures(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Keep
+ private fun customInputGesturesTestArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "META + ALT + Q -> Go Home",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_Q
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ intArrayOf(KeyEvent.KEYCODE_Q),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "META + ALT + Q -> Launch app",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_Q
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_Q),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ ),
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ ),
+ )
+ }
+
+ @Test
+ @Parameters(method = "customInputGesturesTestArguments")
+ fun testCustomKeyGestures(test: TestData) {
+ setupKeyGestureController()
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ test.expectedKeys[0],
+ test.expectedModifierState
+ )
+ )
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ val inputGestureData = builder.build()
+
+ keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
+ testKeyGestureInternal(test)
+ }
+
+ class TouchpadTestData(
+ val name: String,
+ val touchpadGestureType: Int,
+ val expectedKeyGestureType: Int,
+ val expectedAction: Int,
+ val expectedAppLaunchData: AppLaunchData? = null,
+ ) {
+ override fun toString(): String = name
+ }
+
+ @Keep
+ private fun customTouchpadGesturesTestArguments(): Array<TouchpadTestData> {
+ return arrayOf(
+ TouchpadTestData(
+ "3 Finger Tap -> Go Home",
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ ),
+ TouchpadTestData(
+ "3 Finger Tap -> Launch app",
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ ),
+ )
+ }
+
+ @Test
+ @Parameters(method = "customTouchpadGesturesTestArguments")
+ fun testCustomTouchpadGesture(test: TouchpadTestData) {
+ setupKeyGestureController()
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(InputGestureData.createTouchpadTrigger(test.touchpadGestureType))
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ val inputGestureData = builder.build()
+
+ keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
+
+ val handledEvents = mutableListOf<KeyGestureEvent>()
+ val handler = KeyGestureHandler { event, _ ->
+ handledEvents.add(KeyGestureEvent(event))
+ true
+ }
+ keyGestureController.registerKeyGestureHandler(handler, 0)
+ handledEvents.clear()
+
+ keyGestureController.handleTouchpadGesture(test.touchpadGestureType)
+
+ assertEquals(
+ "Test: $test doesn't produce correct number of key gesture events",
+ 1,
+ handledEvents.size
+ )
+ val event = handledEvents[0]
+ assertEquals(
+ "Test: $test doesn't produce correct key gesture type",
+ test.expectedKeyGestureType,
+ event.keyGestureType
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct key gesture action",
+ test.expectedAction,
+ event.action
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct app launch data",
+ test.expectedAppLaunchData,
+ event.appLaunchData
+ )
+
+ keyGestureController.unregisterKeyGestureHandler(handler, 0)
+ }
+
+ private fun testKeyGestureInternal(test: TestData) {
+ val handledEvents = mutableListOf<KeyGestureEvent>()
+ val handler = KeyGestureHandler { event, _ ->
+ handledEvents.add(KeyGestureEvent(event))
+ true
+ }
+ keyGestureController.registerKeyGestureHandler(handler, 0)
+ handledEvents.clear()
+
+ sendKeys(test.keys)
+
+ assertEquals(
+ "Test: $test doesn't produce correct number of key gesture events",
+ test.expectedActions.size,
+ handledEvents.size
+ )
+ for (i in handledEvents.indices) {
+ val event = handledEvents[i]
+ assertArrayEquals(
+ "Test: $test doesn't produce correct key gesture keycodes",
+ test.expectedKeys,
+ event.keycodes
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct key gesture modifier state",
+ test.expectedModifierState,
+ event.modifierState
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct key gesture type",
+ test.expectedKeyGestureType,
+ event.keyGestureType
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct key gesture action",
+ test.expectedActions[i],
+ event.action
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct app launch data",
+ test.expectedAppLaunchData,
+ event.appLaunchData
+ )
+ }
+
+ keyGestureController.unregisterKeyGestureHandler(handler, 0)
+ }
+
+ private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) {
+ var handledEvents = mutableListOf<KeyGestureEvent>()
+ val handler = KeyGestureHandler { event, _ ->
+ handledEvents.add(KeyGestureEvent(event))
+ true
+ }
+ keyGestureController.registerKeyGestureHandler(handler, 0)
+ handledEvents.clear()
+
+ sendKeys(testKeys)
+ assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.size)
+ }
+
+ private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) {
+ var metaState = 0
+ val now = SystemClock.uptimeMillis()
+ for (key in testKeys) {
+ val downEvent = KeyEvent(
+ now, now, KeyEvent.ACTION_DOWN, key, 0 /*repeat*/, metaState,
+ DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
+ InputDevice.SOURCE_KEYBOARD
+ )
+ interceptKey(downEvent, assertNotSentToApps)
+ metaState = metaState or MODIFIER.getOrDefault(key, 0)
+
+ downEvent.recycle()
+ testLooper.dispatchAll()
+ }
+
+ for (key in testKeys.reversed()) {
+ val upEvent = KeyEvent(
+ now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState,
+ DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
+ InputDevice.SOURCE_KEYBOARD
+ )
+ interceptKey(upEvent, assertNotSentToApps)
+ metaState = metaState and MODIFIER.getOrDefault(key, 0).inv()
+
+ upEvent.recycle()
+ testLooper.dispatchAll()
+ }
+ }
+
+ private fun interceptKey(event: KeyEvent, assertNotSentToApps: Boolean) {
+ keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE)
+ testLooper.dispatchAll()
+
+ val consumed =
+ keyGestureController.interceptKeyBeforeDispatching(null, event, 0) == -1L
+ if (assertNotSentToApps) {
+ assertTrue(
+ "interceptKeyBeforeDispatching should consume all events $event",
+ consumed
+ )
+ }
+ if (!consumed) {
+ keyGestureController.interceptUnhandledKey(event, null)
+ }
+ }
+
+ inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() {
+ override fun onKeyGestureEvent(event: AidlKeyGestureEvent) {
+ events.add(KeyGestureEvent(event))
+ }
+ }
+
+ inner class KeyGestureHandler(
+ private var handler: (event: AidlKeyGestureEvent, token: IBinder?) -> Boolean
+ ) : IKeyGestureHandler.Stub() {
+ override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?): Boolean {
+ return handler(event, token)
+ }
+
+ override fun isKeyGestureSupported(gestureType: Int): Boolean {
+ return true
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
index f74fd723d540..4f4c97bef4c0 100644
--- a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt
@@ -18,16 +18,18 @@ package com.android.server.input
import android.content.Context
import android.content.ContextWrapper
-import android.hardware.input.IInputManager
import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import androidx.test.core.app.ApplicationProvider
-import org.junit.After
+import com.android.test.input.MockInputManagerRule
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -35,10 +37,6 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnit
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
private fun createKeyboard(deviceId: Int): InputDevice =
InputDevice.Builder()
@@ -73,15 +71,15 @@ class KeyRemapperTests {
@get:Rule
val rule = MockitoJUnit.rule()!!
- @Mock
- private lateinit var iInputManager: IInputManager
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
+
@Mock
private lateinit var native: NativeInputManagerService
private lateinit var mKeyRemapper: KeyRemapper
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
@Before
fun setup() {
@@ -104,25 +102,17 @@ class KeyRemapperTests {
dataStore,
testLooper.looper
)
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
- Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
- }
-
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
+ Mockito.`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
}
@Test
fun testKeyRemapping_whenRemappingEnabled() {
ModifierRemappingFlag(true).use {
val keyboard = createKeyboard(DEVICE_ID)
- Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
for (i in REMAPPABLE_KEYS.indices) {
val fromKeyCode = REMAPPABLE_KEYS[i]
@@ -160,7 +150,7 @@ class KeyRemapperTests {
fun testKeyRemapping_whenRemappingDisabled() {
ModifierRemappingFlag(false).use {
val keyboard = createKeyboard(DEVICE_ID)
- Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
mKeyRemapper.remapKey(REMAPPABLE_KEYS[0], REMAPPABLE_KEYS[1])
testLooper.dispatchAll()
diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 59aa96c46336..58fb4e1ed103 100644
--- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -20,11 +20,9 @@ import android.animation.ValueAnimator
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Color
-import android.hardware.input.IInputManager
import android.hardware.input.IKeyboardBacklightListener
import android.hardware.input.IKeyboardBacklightState
import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
import android.hardware.lights.Light
import android.os.UEventObserver
import android.os.test.TestLooper
@@ -35,7 +33,11 @@ import androidx.test.core.app.ApplicationProvider
import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
-import org.junit.After
+import com.android.test.input.MockInputManagerRule
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
@@ -52,10 +54,6 @@ import org.mockito.Mockito.eq
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
private fun createKeyboard(deviceId: Int): InputDevice =
InputDevice.Builder()
@@ -100,10 +98,10 @@ class KeyboardBacklightControllerTests {
@get:Rule
val rule = MockitoJUnit.rule()!!
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
@Mock
- private lateinit var iInputManager: IInputManager
- @Mock
private lateinit var native: NativeInputManagerService
@Mock
private lateinit var uEventManager: UEventManager
@@ -111,7 +109,6 @@ class KeyboardBacklightControllerTests {
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private var lightColorMap: HashMap<Int, Int> = HashMap()
private var lastBacklightState: KeyboardBacklightState? = null
private var sysfsNodeChanges = 0
@@ -134,10 +131,9 @@ class KeyboardBacklightControllerTests {
testLooper = TestLooper()
keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
testLooper.looper, FakeAnimatorFactory(), uEventManager)
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
- `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+ `when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
`when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then {
val args = it.arguments
lightColorMap.put(args[1] as Int, args[2] as Int)
@@ -152,13 +148,6 @@ class KeyboardBacklightControllerTests {
}
}
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
- }
-
@Test
fun testKeyboardBacklightIncrementDecrement() {
KeyboardBacklightFlags(
@@ -168,8 +157,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
@@ -186,8 +176,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithoutBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
incrementKeyboardBacklight(DEVICE_ID)
@@ -205,8 +196,9 @@ class KeyboardBacklightControllerTests {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(
listOf(
keyboardBacklight,
keyboardInputLight
@@ -230,8 +222,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
dataStore.setKeyboardBacklightBrightness(
@@ -263,7 +256,8 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
dataStore.setKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor,
LIGHT_ID,
@@ -278,7 +272,7 @@ class KeyboardBacklightControllerTests {
lightColorMap.isEmpty()
)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
keyboardBacklightController.notifyUserActivity()
testLooper.dispatchNext()
@@ -300,8 +294,9 @@ class KeyboardBacklightControllerTests {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
// Register backlight listener
@@ -352,8 +347,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
dataStore.setKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor,
LIGHT_ID,
@@ -388,8 +384,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
dataStore.setKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor,
LIGHT_ID,
@@ -482,8 +479,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
incrementKeyboardBacklight(DEVICE_ID)
@@ -511,8 +509,9 @@ class KeyboardBacklightControllerTests {
val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
suggestedLevels)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
@@ -531,8 +530,9 @@ class KeyboardBacklightControllerTests {
val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
suggestedLevels)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
@@ -551,8 +551,9 @@ class KeyboardBacklightControllerTests {
val suggestedLevels = intArrayOf(22, 63, 135, 196)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
suggestedLevels)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
// Framework will add the lowest and maximum levels if not provided via config
@@ -572,8 +573,10 @@ class KeyboardBacklightControllerTests {
val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
suggestedLevels)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID))
+ .thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
// Framework will drop out of bound levels in the config
@@ -591,8 +594,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
dataStore.setKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor,
@@ -619,8 +623,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
incrementKeyboardBacklight(DEVICE_ID)
@@ -642,8 +647,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
sendAmbientBacklightValue(1)
assertEquals(
@@ -671,8 +677,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
sendAmbientBacklightValue(254)
assertEquals(
@@ -701,8 +708,9 @@ class KeyboardBacklightControllerTests {
).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID))
+ .thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
incrementKeyboardBacklight(DEVICE_ID)
assertEquals(
diff --git a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
new file mode 100644
index 000000000000..5da0beb9cc8a
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2024 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.hardware.input.InputManager
+import android.hardware.input.KeyGlyphMap.KeyCombination
+import android.os.Bundle
+import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.hardware.input.Flags
+import com.android.test.input.MockInputManagerRule
+import com.android.test.input.R
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for custom keyboard glyph map configuration.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardGlyphManagerTests
+ */
+@Presubmit
+class KeyboardGlyphManagerTests {
+
+ companion object {
+ const val DEVICE_ID = 1
+ const val VENDOR_ID = 0x1234
+ const val PRODUCT_ID = 0x3456
+ const val DEVICE_ID2 = 2
+ const val VENDOR_ID2 = 0x1235
+ const val PRODUCT_ID2 = 0x3457
+ const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
+ const val RECEIVER_NAME = "DummyReceiver"
+ }
+
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+ @get:Rule
+ val mockitoRule = MockitoJUnit.rule()!!
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private lateinit var keyboardGlyphManager: KeyboardGlyphManager
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+ private lateinit var keyboardDevice: InputDevice
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ testLooper = TestLooper()
+ keyboardGlyphManager = KeyboardGlyphManager(context, testLooper.looper)
+
+ setupInputDevices()
+ setupBroadcastReceiver()
+ keyboardGlyphManager.systemRunning()
+ testLooper.dispatchAll()
+ }
+
+ private fun setupInputDevices() {
+ val inputManager = InputManager(context)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+
+ keyboardDevice = createKeyboard(DEVICE_ID, VENDOR_ID, PRODUCT_ID, 0, "", "")
+ Mockito.`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, DEVICE_ID2))
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+
+ val keyboardDevice2 = createKeyboard(DEVICE_ID2, VENDOR_ID2, PRODUCT_ID2, 0, "", "")
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID2)).thenReturn(keyboardDevice2)
+ }
+
+ private fun setupBroadcastReceiver() {
+ Mockito.`when`(context.packageManager).thenReturn(packageManager)
+
+ val info = createMockReceiver()
+ Mockito.`when`(packageManager.queryBroadcastReceiversAsUser(Mockito.any(), Mockito.anyInt(),
+ Mockito.anyInt())).thenReturn(listOf(info))
+ Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
+ .thenReturn(info.activityInfo)
+
+ val resources = context.resources
+ Mockito.`when`(
+ packageManager.getResourcesForApplication(
+ Mockito.any(
+ ApplicationInfo::class.java
+ )
+ )
+ ).thenReturn(resources)
+ }
+
+ private fun createMockReceiver(): ResolveInfo {
+ val info = ResolveInfo()
+ info.activityInfo = ActivityInfo()
+ info.activityInfo.packageName = PACKAGE_NAME
+ info.activityInfo.name = RECEIVER_NAME
+ info.activityInfo.applicationInfo = ApplicationInfo()
+ info.activityInfo.metaData = Bundle()
+ info.activityInfo.metaData.putInt(
+ InputManager.META_DATA_KEYBOARD_GLYPH_MAPS,
+ R.xml.keyboard_glyph_maps
+ )
+ info.serviceInfo = ServiceInfo()
+ info.serviceInfo.packageName = PACKAGE_NAME
+ info.serviceInfo.name = RECEIVER_NAME
+ return info
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_GLYPH_MAP)
+ fun testGlyphMapsLoaded() {
+ assertNotNull(
+ "Glyph map for test keyboard(deviceId=$DEVICE_ID) must exist",
+ keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID)
+ )
+ assertNotNull(
+ "Glyph map for test keyboard(deviceId=$DEVICE_ID2) must exist",
+ keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID2)
+ )
+ assertNull(
+ "Glyph map for non-existing keyboard must be null",
+ keyboardGlyphManager.getKeyGlyphMap(-2)
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_GLYPH_MAP)
+ fun testGlyphMapCorrectlyLoaded() {
+ val glyphMap = keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID)
+ // Test glyph map used in this test: {@see test_glyph_map.xml}
+ assertNotNull(glyphMap!!.getDrawableForKeycode(context, KeyEvent.KEYCODE_BACK))
+
+ assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_LEFT))
+ assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_RIGHT))
+ assertNotNull(glyphMap.getDrawableForModifierState(context, KeyEvent.META_META_ON))
+
+ val functionRowKeys = glyphMap.functionRowKeys
+ assertEquals(1, functionRowKeys.size)
+ assertEquals(KeyEvent.KEYCODE_EMOJI_PICKER, functionRowKeys[0])
+
+ val hardwareShortcuts = glyphMap.hardwareShortcuts
+ assertEquals(2, hardwareShortcuts.size)
+ assertEquals(
+ KeyEvent.KEYCODE_BACK,
+ hardwareShortcuts[KeyCombination(KeyEvent.META_FUNCTION_ON, KeyEvent.KEYCODE_1)]
+ )
+ assertEquals(
+ KeyEvent.KEYCODE_HOME,
+ hardwareShortcuts[
+ KeyCombination(
+ KeyEvent.META_FUNCTION_ON or KeyEvent.META_META_ON,
+ KeyEvent.KEYCODE_2
+ )
+ ]
+ )
+ }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 93f97cb4a7ee..d6654cceb458 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -24,11 +24,10 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
-import android.hardware.input.KeyboardLayoutSelectionResult
-import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
import android.hardware.input.KeyboardLayout
+import android.hardware.input.KeyboardLayoutSelectionResult
import android.icu.util.ULocale
import android.os.Bundle
import android.os.test.TestLooper
@@ -42,8 +41,12 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.os.KeyboardConfiguredProto
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.test.input.MockInputManagerRule
import com.android.test.input.R
-import org.junit.After
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
@@ -53,12 +56,8 @@ import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
-private fun createKeyboard(
+fun createKeyboard(
deviceId: Int,
vendorId: Int,
productId: Int,
@@ -120,8 +119,8 @@ class KeyboardLayoutManagerTests {
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
.mockStatic(FrameworkStatsLog::class.java).build()!!
- @Mock
- private lateinit var iInputManager: IInputManager
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
@Mock
private lateinit var native: NativeInputManagerService
@@ -148,7 +147,6 @@ class KeyboardLayoutManagerTests {
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
override fun openRead(): InputStream? {
throw FileNotFoundException()
@@ -171,13 +169,6 @@ class KeyboardLayoutManagerTests {
setupIme()
}
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
- }
-
private fun setupInputDevices() {
val inputManager = InputManager(context)
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
@@ -191,19 +182,19 @@ class KeyboardLayoutManagerTests {
DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "dvorak")
englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "qwerty")
- Mockito.`when`(iInputManager.inputDeviceIds)
+ Mockito.`when`(inputManagerRule.mock.inputDeviceIds)
.thenReturn(intArrayOf(
DEVICE_ID,
VENDOR_SPECIFIC_DEVICE_ID,
ENGLISH_DVORAK_DEVICE_ID,
ENGLISH_QWERTY_DEVICE_ID
))
- Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
- Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
.thenReturn(vendorSpecificKeyboardDevice)
- Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
.thenReturn(englishDvorakKeyboardDevice)
- Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
+ Mockito.`when`(inputManagerRule.mock.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
.thenReturn(englishQwertyKeyboardDevice)
}
diff --git a/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt
new file mode 100644
index 000000000000..47e7ac720a08
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2024 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import android.view.PointerIcon
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Tests for {@link PointerIconCache}.
+ */
+@Presubmit
+class PointerIconCacheTest {
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+ @Mock
+ private lateinit var defaultDisplay: Display
+
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+ private lateinit var cache: PointerIconCache
+
+ @Before
+ fun setup() {
+ whenever(defaultDisplay.displayId).thenReturn(Display.DEFAULT_DISPLAY)
+
+ context = object : ContextWrapper(InstrumentationRegistry.getInstrumentation().context) {
+ override fun getDisplay() = defaultDisplay
+ }
+
+ testLooper = TestLooper()
+ cache = PointerIconCache(context, native, Handler(testLooper.looper))
+ }
+
+ @Test
+ fun testSetPointerScale() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ cache.setPointerScale(2f)
+
+ testLooper.dispatchAll()
+ verify(native).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+ assertEquals(defaultBitmap.height * 2, bitmap.height)
+ assertEquals(defaultBitmap.width * 2, bitmap.width)
+ }
+
+ @Test
+ fun testSetAccessibilityScaleFactor() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 4f)
+
+ testLooper.dispatchAll()
+ verify(native).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+ assertEquals(defaultBitmap.height * 4, bitmap.height)
+ assertEquals(defaultBitmap.width * 4, bitmap.width)
+ }
+
+ @Test
+ fun testSetAccessibilityScaleFactorOnSecondaryDisplay() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ val secondaryDisplayId = Display.DEFAULT_DISPLAY + 1
+ cache.setAccessibilityScaleFactor(secondaryDisplayId, 4f)
+
+ testLooper.dispatchAll()
+ verify(native).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+ assertEquals(defaultBitmap.height, bitmap.height)
+ assertEquals(defaultBitmap.width, bitmap.width)
+
+ val bitmapSecondary =
+ cache.getLoadedPointerIcon(secondaryDisplayId, PointerIcon.TYPE_ARROW).bitmap
+ assertEquals(defaultBitmap.height * 4, bitmapSecondary.height)
+ assertEquals(defaultBitmap.width * 4, bitmapSecondary.width)
+ }
+
+ @Test
+ fun testSetPointerScaleAndAccessibilityScaleFactor() {
+ val defaultBitmap = getDefaultIcon().bitmap
+ cache.setPointerScale(2f)
+ cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 3f)
+
+ testLooper.dispatchAll()
+ verify(native, times(2)).reloadPointerIcons()
+
+ val bitmap =
+ cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap
+
+ assertEquals(defaultBitmap.height * 6, bitmap.height)
+ assertEquals(defaultBitmap.width * 6, bitmap.width)
+ }
+
+ private fun getDefaultIcon() =
+ PointerIcon.getLoadedSystemIcon(context, PointerIcon.TYPE_ARROW, false, 1f)
+}
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
new file mode 100644
index 000000000000..5875520cd259
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2024 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.input.debug;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.InputDevice;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.input.InputManagerService;
+import com.android.server.input.TouchpadHardwareProperties;
+
+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;
+
+/**
+ * Build/Install/Run:
+ * atest TouchpadDebugViewControllerTests
+ */
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class TouchpadDebugViewControllerTests {
+ private static final int DEVICE_ID = 1000;
+ private static final String TAG = "TouchpadDebugViewController";
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private Context mContext;
+ private TouchpadDebugViewController mTouchpadDebugViewController;
+ @Mock
+ private InputManager mInputManagerMock;
+ @Mock
+ private InputManagerService mInputManagerServiceMock;
+ @Mock
+ private WindowManager mWindowManagerMock;
+ private TestableLooper mTestableLooper;
+
+ @Before
+ public void setup() throws Exception {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ TestableContext mTestableContext = new TestableContext(mContext);
+ mTestableContext.addMockSystemService(WindowManager.class, mWindowManagerMock);
+
+ Rect bounds = new Rect(0, 0, 2560, 1600);
+ WindowMetrics metrics = new WindowMetrics(bounds, new WindowInsets(bounds), 1.0f);
+
+ when(mWindowManagerMock.getCurrentWindowMetrics()).thenReturn(metrics);
+
+ unMockTouchpad();
+
+ mTestableLooper = TestableLooper.get(this);
+
+ mTestableContext.addMockSystemService(InputManager.class, mInputManagerMock);
+ when(mInputManagerServiceMock.getTouchpadHardwareProperties(DEVICE_ID)).thenReturn(
+ new TouchpadHardwareProperties.Builder(-100f, 100f, -100f, 100f, 45f, 45f, -5f, 5f,
+ (short) 10, true, false).build());
+
+ mTouchpadDebugViewController = new TouchpadDebugViewController(mTestableContext,
+ mTestableLooper.getLooper(), mInputManagerServiceMock);
+ }
+
+ private InputDevice createTouchpadInputDevice(int id) {
+ return new InputDevice.Builder()
+ .setId(id)
+ .setSources(InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)
+ .setName("Test Device " + id)
+ .build();
+ }
+
+ private void mockTouchpad() {
+ when(mInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+ when(mInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+ createTouchpadInputDevice(DEVICE_ID));
+ }
+
+ private void unMockTouchpad() {
+ when(mInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{});
+ when(mInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(null);
+ }
+
+ @Test
+ public void touchpadConnectedWhileSettingDisabled() throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+ mockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+ verify(mWindowManagerMock, never()).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+ }
+
+ @Test
+ public void settingEnabledWhileNoTouchpadConnected() throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+ verify(mWindowManagerMock, never()).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+ }
+
+ @Test
+ public void touchpadConnectedWhileSettingEnabled() throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+ mockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+ }
+
+ @Test
+ public void touchpadConnectedWhileSettingEnabledThenDisabled() throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+ mockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, times(1)).removeView(any());
+ }
+
+ @Test
+ public void touchpadConnectedWhileSettingDisabledThenEnabled() throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+ mockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+ verify(mWindowManagerMock, never()).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+ }
+
+ @Test
+ public void touchpadConnectedWhileSettingDisabledThenTouchpadDisconnected() throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+ mockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+ verify(mWindowManagerMock, never()).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+
+ unMockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceRemoved(DEVICE_ID);
+
+ verify(mWindowManagerMock, never()).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+ }
+
+ @Test
+ public void touchpadConnectedWhileSettingEnabledThenTouchpadDisconnectedThenSettingDisabled()
+ throws Exception {
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+ mockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, never()).removeView(any());
+
+ unMockTouchpad();
+ mTouchpadDebugViewController.onInputDeviceRemoved(DEVICE_ID);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, times(1)).removeView(any());
+
+ mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+ verify(mWindowManagerMock, times(1)).addView(any(), any());
+ verify(mWindowManagerMock, times(1)).removeView(any());
+ }
+}
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
new file mode 100644
index 000000000000..60fa52f85e34
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2024 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.input.debug;
+
+import static android.view.InputDevice.SOURCE_MOUSE;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.input.InputManager;
+import android.testing.TestableContext;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+import android.widget.TextView;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.input.MotionEventBuilder;
+import com.android.cts.input.PointerBuilder;
+import com.android.server.input.TouchpadFingerState;
+import com.android.server.input.TouchpadHardwareProperties;
+import com.android.server.input.TouchpadHardwareState;
+
+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.function.Consumer;
+
+/**
+ * Build/Install/Run:
+ * atest TouchpadDebugViewTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TouchpadDebugViewTest {
+ private static final int TOUCHPAD_DEVICE_ID = 60;
+
+ private TouchpadDebugView mTouchpadDebugView;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ @Mock
+ WindowManager mWindowManager;
+ @Mock
+ InputManager mInputManager;
+
+ Rect mWindowBounds;
+ WindowMetrics mWindowMetrics;
+ TestableContext mTestableContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mTestableContext = new TestableContext(context);
+
+ mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
+ mTestableContext.addMockSystemService(InputManager.class, mInputManager);
+
+ mWindowBounds = new Rect(0, 0, 2560, 1600);
+ mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f);
+
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+
+ InputDevice inputDevice = new InputDevice.Builder()
+ .setId(TOUCHPAD_DEVICE_ID)
+ .setSources(InputDevice.SOURCE_TOUCHPAD | SOURCE_MOUSE)
+ .setName("Test Device " + TOUCHPAD_DEVICE_ID)
+ .build();
+
+ when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
+
+ Consumer<Integer> touchpadSwitchHandler = id -> {};
+
+ mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
+ new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
+ 500f, 45f, 47f, -4f, 5f, (short) 10, true,
+ true).build(), touchpadSwitchHandler);
+
+ mTouchpadDebugView.measure(
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ );
+
+ doAnswer(invocation -> {
+ mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+ mTouchpadDebugView.getMeasuredHeight());
+ return null;
+ }).when(mWindowManager).addView(any(), any());
+
+ doAnswer(invocation -> {
+ mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+ mTouchpadDebugView.getMeasuredHeight());
+ return null;
+ }).when(mWindowManager).updateViewLayout(any(), any());
+
+ mWindowLayoutParams = mTouchpadDebugView.getWindowLayoutParams();
+ mWindowLayoutParams.x = 20;
+ mWindowLayoutParams.y = 20;
+
+ mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+ mTouchpadDebugView.getMeasuredHeight());
+ }
+
+ @Test
+ public void testDragView() {
+ // Initial view position relative to screen.
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+ float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+ // Simulate ACTION_DOWN event (initial touch).
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+
+ // Simulate ACTION_MOVE event (dragging to the right).
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+ verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+ // Verify position after ACTION_MOVE
+ assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+
+ // Simulate ACTION_UP event (release touch).
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+ }
+
+ @Test
+ public void testDragViewOutOfBounds() {
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + 10f)
+ .y(initialY + 10f)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+
+ // Simulate ACTION_MOVE event (dragging far to the right and bottom, beyond screen bounds)
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(mWindowBounds.width() + mTouchpadDebugView.getWidth())
+ .y(mWindowBounds.height() + mTouchpadDebugView.getHeight())
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+ verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+ // Verify the view has been clamped to the right and bottom edges of the screen
+ assertEquals(mWindowBounds.width() - mTouchpadDebugView.getWidth(),
+ mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(mWindowBounds.height() - mTouchpadDebugView.getHeight(),
+ mWindowLayoutParamsCaptor.getValue().y);
+
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(mWindowBounds.width() + mTouchpadDebugView.getWidth())
+ .y(mWindowBounds.height() + mTouchpadDebugView.getHeight())
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // Verify the view has been clamped to the right and bottom edges of the screen
+ assertEquals(mWindowBounds.width() - mTouchpadDebugView.getWidth(),
+ mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(mWindowBounds.height() - mTouchpadDebugView.getHeight(),
+ mWindowLayoutParamsCaptor.getValue().y);
+ }
+
+ @Test
+ public void testSlopOffset() {
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() / 2.0f;
+ float offsetY = -(ViewConfiguration.get(mTestableContext).getScaledTouchSlop() / 2.0f);
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX)
+ .y(initialY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + offsetX)
+ .y(initialY + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX)
+ .y(initialY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // In this case the updateViewLayout() method wouldn't be called because the drag
+ // distance hasn't exceeded the slop
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+ }
+
+ @Test
+ public void testViewReturnsToInitialPositionOnCancel() {
+ int initialX = mWindowLayoutParams.x;
+ int initialY = mWindowLayoutParams.y;
+
+ float offsetX = 50;
+ float offsetY = 50;
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX)
+ .y(initialY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + offsetX)
+ .y(initialY + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+ verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+ assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+
+ // Simulate ACTION_CANCEL event (canceling the touch event stream)
+ MotionEvent actionCancel = new MotionEventBuilder(MotionEvent.ACTION_CANCEL,
+ SOURCE_TOUCHSCREEN)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(initialX + offsetX)
+ .y(initialY + offsetY)
+ )
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionCancel);
+
+ // Verify the view returns to its initial position
+ verify(mWindowManager, times(2)).updateViewLayout(any(),
+ mWindowLayoutParamsCaptor.capture());
+ assertEquals(initialX, mWindowLayoutParamsCaptor.getValue().x);
+ assertEquals(initialY, mWindowLayoutParamsCaptor.getValue().y);
+ }
+
+ @Test
+ public void testTouchpadClick() {
+ View child = mTouchpadDebugView.getChildAt(0);
+
+ mTouchpadDebugView.updateHardwareState(
+ new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
+ new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
+
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#769763"));
+
+ mTouchpadDebugView.updateHardwareState(
+ new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
+ new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
+
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#5455A9"));
+
+ mTouchpadDebugView.updateHardwareState(
+ new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
+ new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
+
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#769763"));
+
+ // Color should not change because hardware state of a different touchpad
+ mTouchpadDebugView.updateHardwareState(
+ new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
+ new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);
+
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#769763"));
+ }
+
+ @Test
+ public void testTouchpadGesture() {
+ int gestureType = 3;
+ TextView child = mTouchpadDebugView.getGestureInfoView();
+
+ mTouchpadDebugView.updateGestureInfo(gestureType, TOUCHPAD_DEVICE_ID);
+ assertEquals(child.getText().toString(), TouchpadDebugView.getGestureText(gestureType));
+
+ gestureType = 6;
+ mTouchpadDebugView.updateGestureInfo(gestureType, TOUCHPAD_DEVICE_ID);
+ assertEquals(child.getText().toString(), TouchpadDebugView.getGestureText(gestureType));
+ }
+
+ @Test
+ public void testTwoFingerDrag() {
+ float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+ float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+ // Simulate ACTION_DOWN event (gesture starts).
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ // Simulate ACTION_MOVE event (dragging with two fingers, processed as one pointer).
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ // Simulate ACTION_UP event (gesture ends).
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f + offsetX)
+ .y(40f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+ }
+
+ @Test
+ public void testPinchDrag() {
+ float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+ MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+ MotionEvent pointerDown = new MotionEventBuilder(MotionEvent.ACTION_POINTER_DOWN,
+ SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f)
+ )
+ .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(45f)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(pointerDown);
+
+ // Simulate ACTION_MOVE event (both fingers moving apart).
+ MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f - offsetY)
+ )
+ .rawXCursorPosition(mWindowLayoutParams.x + 10f)
+ .rawYCursorPosition(mWindowLayoutParams.y + 10f)
+ .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(45f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+ MotionEvent pointerUp = new MotionEventBuilder(MotionEvent.ACTION_POINTER_UP, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f - offsetY)
+ )
+ .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(45f + offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(pointerUp);
+
+ MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE)
+ .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+ .x(40f)
+ .y(40f - offsetY)
+ )
+ .classification(MotionEvent.CLASSIFICATION_PINCH)
+ .build();
+ mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+ // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
+ verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index d32cedb24a36..cd6ab30d8678 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -166,12 +166,12 @@ class AnrTest {
val displayManager =
instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val display = displayManager.getDisplay(obj.getDisplayId())
- val touchScreen = UinputTouchScreen(instrumentation, display)
-
val rect: Rect = obj.visibleBounds
- val pointer = touchScreen.touchDown(rect.centerX(), rect.centerY())
- pointer.lift()
- touchScreen.close()
+ UinputTouchScreen(instrumentation, display).use { touchScreen ->
+ touchScreen
+ .touchDown(rect.centerX(), rect.centerY())
+ .lift()
+ }
}
private fun triggerAnr() {
diff --git a/tests/Input/src/com/android/test/input/CaptureEventActivity.kt b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt
new file mode 100644
index 000000000000..d54e3470d9c4
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/CaptureEventActivity.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 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.test.input
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.InputEvent
+import android.view.KeyEvent
+import android.view.MotionEvent
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertNull
+
+class CaptureEventActivity : Activity() {
+ private val events = LinkedBlockingQueue<InputEvent>()
+ var shouldHandleKeyEvents = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Set the fixed orientation if requested
+ if (intent.hasExtra(EXTRA_FIXED_ORIENTATION)) {
+ val orientation = intent.getIntExtra(EXTRA_FIXED_ORIENTATION, 0)
+ setRequestedOrientation(orientation)
+ }
+
+ // Set the flag if requested
+ if (intent.hasExtra(EXTRA_WINDOW_FLAGS)) {
+ val flags = intent.getIntExtra(EXTRA_WINDOW_FLAGS, 0)
+ window.addFlags(flags)
+ }
+ }
+
+ override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
+ events.add(MotionEvent.obtain(ev))
+ return true
+ }
+
+ override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+ events.add(MotionEvent.obtain(ev))
+ return true
+ }
+
+ override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
+ events.add(KeyEvent(event))
+ return shouldHandleKeyEvents
+ }
+
+ override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
+ events.add(MotionEvent.obtain(ev))
+ return true
+ }
+
+ fun getInputEvent(): InputEvent? {
+ return events.poll(5, TimeUnit.SECONDS)
+ }
+
+ fun hasReceivedEvents(): Boolean {
+ return !events.isEmpty()
+ }
+
+ fun assertNoEvents() {
+ val event = events.poll(100, TimeUnit.MILLISECONDS)
+ assertNull("Expected no events, but received $event", event)
+ }
+
+ companion object {
+ const val EXTRA_FIXED_ORIENTATION = "fixed_orientation"
+ const val EXTRA_WINDOW_FLAGS = "window_flags"
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
index c1a86b3a2dac..015e188fc98e 100644
--- a/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
+++ b/tests/Input/src/com/android/test/input/InputEventAssignerTest.kt
@@ -18,12 +18,20 @@ package com.android.test.input
import android.view.InputDevice.SOURCE_MOUSE
import android.view.InputDevice.SOURCE_TOUCHSCREEN
+import android.view.InputDevice.SOURCE_STYLUS
+import android.view.InputDevice.SOURCE_TOUCHPAD
+
import android.view.InputEventAssigner
import android.view.KeyEvent
import android.view.MotionEvent
import org.junit.Assert.assertEquals
import org.junit.Test
+sealed class StreamEvent
+private data object Vsync : StreamEvent()
+data class MotionEventData(val action: Int, val source: Int, val id: Int, val expectedId: Int) :
+ StreamEvent()
+
/**
* Create a MotionEvent with the provided action, eventTime, and source
*/
@@ -49,64 +57,164 @@ private fun createKeyEvent(action: Int, eventTime: Long): KeyEvent {
return KeyEvent(eventTime, eventTime, action, code, repeat)
}
+/**
+ * Check that the correct eventIds are assigned in a stream. The stream consists of motion
+ * events or vsync (processed frame)
+ * Each streamEvent should have unique ids when writing tests
+ * The test passes even if two events get assigned the same eventId, since the mapping is
+ * streamEventId -> motionEventId and streamEvents have unique ids
+ */
+private fun checkEventStream(vararg streamEvents: StreamEvent) {
+ val assigner = InputEventAssigner()
+ var eventTime = 10L
+ // Maps MotionEventData.id to MotionEvent.id
+ // We can't control the event id of the generated motion events but for testing it's easier
+ // to label the events with a custom id for readability
+ val eventIdMap: HashMap<Int, Int> = HashMap()
+ for (streamEvent in streamEvents) {
+ when (streamEvent) {
+ is MotionEventData -> {
+ val event = createMotionEvent(streamEvent.action, eventTime, streamEvent.source)
+ eventIdMap[streamEvent.id] = event.id
+ val eventId = assigner.processEvent(event)
+ assertEquals(eventIdMap[streamEvent.expectedId], eventId)
+ }
+ is Vsync -> assigner.notifyFrameProcessed()
+ }
+ eventTime += 1
+ }
+}
+
class InputEventAssignerTest {
companion object {
private const val TAG = "InputEventAssignerTest"
}
/**
- * A single MOVE event should be assigned to the next available frame.
+ * A single event should be assigned to the next available frame.
*/
@Test
- fun testTouchGesture() {
- val assigner = InputEventAssigner()
- val event = createMotionEvent(MotionEvent.ACTION_MOVE, 10, SOURCE_TOUCHSCREEN)
- val eventId = assigner.processEvent(event)
- assertEquals(event.id, eventId)
+ fun testTouchMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testMouseMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_MOUSE, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testMouseScroll() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testStylusMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testStylusHover() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_HOVER_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1)
+ )
+ }
+
+ @Test
+ fun testTouchpadMove() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_MOVE, SOURCE_STYLUS, id = 1, expectedId = 1)
+ )
}
/**
- * DOWN event should be used until a vsync comes in. After vsync, the latest event should be
- * produced.
+ * Test that before a VSYNC the event id generated by input event assigner for move events is
+ * the id of the down event. Move events coming after a VSYNC should be assigned their own event
+ * id
*/
+ private fun testDownAndMove(source: Int) {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, source, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_MOVE, source, id = 2, expectedId = 1),
+ Vsync,
+ MotionEventData(MotionEvent.ACTION_MOVE, source, id = 4, expectedId = 4)
+ )
+ }
+
@Test
- fun testTouchDownWithMove() {
- val assigner = InputEventAssigner()
- val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_TOUCHSCREEN)
- val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_TOUCHSCREEN)
- val move2 = createMotionEvent(MotionEvent.ACTION_MOVE, 13, SOURCE_TOUCHSCREEN)
- val move3 = createMotionEvent(MotionEvent.ACTION_MOVE, 14, SOURCE_TOUCHSCREEN)
- val move4 = createMotionEvent(MotionEvent.ACTION_MOVE, 15, SOURCE_TOUCHSCREEN)
- var eventId = assigner.processEvent(down)
- assertEquals(down.id, eventId)
- eventId = assigner.processEvent(move1)
- assertEquals(down.id, eventId)
- eventId = assigner.processEvent(move2)
- // Even though we already had 2 move events, there was no choreographer callback yet.
- // Therefore, we should still get the id of the down event
- assertEquals(down.id, eventId)
+ fun testTouchDownAndMove() {
+ testDownAndMove(SOURCE_TOUCHSCREEN)
+ }
- // Now send CALLBACK_INPUT to the assigner. It should provide the latest motion event
- assigner.notifyFrameProcessed()
- eventId = assigner.processEvent(move3)
- assertEquals(move3.id, eventId)
- eventId = assigner.processEvent(move4)
- assertEquals(move4.id, eventId)
+ @Test
+ fun testMouseDownAndMove() {
+ testDownAndMove(SOURCE_MOUSE)
+ }
+
+ @Test
+ fun testStylusDownAndMove() {
+ testDownAndMove(SOURCE_STYLUS)
+ }
+
+ @Test
+ fun testTouchpadDownAndMove() {
+ testDownAndMove(SOURCE_TOUCHPAD)
}
/**
- * Similar to the above test, but with SOURCE_MOUSE. Since we don't have down latency
- * concept for non-touchscreens, the latest input event will be used.
+ * After an up event, motion events should be assigned their own event id
*/
@Test
- fun testMouseDownWithMove() {
- val assigner = InputEventAssigner()
- val down = createMotionEvent(MotionEvent.ACTION_DOWN, 10, SOURCE_MOUSE)
- val move1 = createMotionEvent(MotionEvent.ACTION_MOVE, 12, SOURCE_MOUSE)
- var eventId = assigner.processEvent(down)
- assertEquals(down.id, eventId)
- eventId = assigner.processEvent(move1)
- assertEquals(move1.id, eventId)
+ fun testMouseDownUpAndScroll() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_UP, SOURCE_MOUSE, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3)
+ )
+ }
+
+ /**
+ * After an up event, motion events should be assigned their own event id
+ */
+ @Test
+ fun testStylusDownUpAndHover() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_UP, SOURCE_STYLUS, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3)
+ )
+ }
+
+ /**
+ * After a cancel event, motion events should be assigned their own event id
+ */
+ @Test
+ fun testMouseDownCancelAndScroll() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_MOUSE, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_MOUSE, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_SCROLL, SOURCE_MOUSE, id = 3, expectedId = 3)
+ )
+ }
+
+ /**
+ * After a cancel event, motion events should be assigned their own event id
+ */
+ @Test
+ fun testStylusDownCancelAndHover() {
+ checkEventStream(
+ MotionEventData(MotionEvent.ACTION_DOWN, SOURCE_STYLUS, id = 1, expectedId = 1),
+ MotionEventData(MotionEvent.ACTION_CANCEL, SOURCE_STYLUS, id = 2, expectedId = 2),
+ MotionEventData(MotionEvent.ACTION_HOVER_ENTER, SOURCE_STYLUS, id = 3, expectedId = 3)
+ )
}
/**
diff --git a/tests/Input/src/com/android/test/input/MockInputManagerRule.kt b/tests/Input/src/com/android/test/input/MockInputManagerRule.kt
new file mode 100644
index 000000000000..cef985600c40
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/MockInputManagerRule.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 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.test.input
+
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManagerGlobal
+import org.junit.rules.ExternalResource
+import org.mockito.Mockito
+
+/**
+ * A test rule that temporarily replaces the [IInputManager] connection to the server with a mock
+ * to be used for testing.
+ */
+class MockInputManagerRule : ExternalResource() {
+
+ private lateinit var session: InputManagerGlobal.TestSession
+
+ val mock: IInputManager = Mockito.mock(IInputManager::class.java)
+
+ override fun before() {
+ session = InputManagerGlobal.createTestSession(mock)
+ }
+
+ override fun after() {
+ if (this::session.isInitialized) {
+ session.close()
+ }
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
index d196b85a7466..abfe549f3d22 100644
--- a/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
+++ b/tests/Input/src/com/android/test/input/PointerIconLoadingTest.kt
@@ -19,7 +19,6 @@ package com.android.test.input
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
-import android.os.Environment
import android.view.ContextThemeWrapper
import android.view.PointerIcon
import android.view.flags.Flags.enableVectorCursorA11ySettings
@@ -88,6 +87,35 @@ class PointerIconLoadingTest {
theme.applyStyle(
PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_GREEN),
/* force= */ true)
+ theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(
+ PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE), /* force= */ true)
+
+ val pointerIcon =
+ PointerIcon.getLoadedSystemIcon(
+ ContextThemeWrapper(context, theme),
+ PointerIcon.TYPE_ARROW,
+ /* useLargeIcons= */ false,
+ /* pointerScale= */ 1f)
+
+ pointerIcon.getBitmap().assertAgainstGolden(
+ screenshotRule,
+ testName.methodName,
+ exactScreenshotMatcher
+ )
+ }
+
+ @Test
+ fun testPointerStrokeStyle() {
+ assumeTrue(enableVectorCursors())
+ assumeTrue(enableVectorCursorA11ySettings())
+
+ val theme: Resources.Theme = context.getResources().newTheme()
+ theme.setTo(context.getTheme())
+ theme.applyStyle(
+ PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK),
+ /* force= */ true)
+ theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(
+ PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_BLACK), /* force= */ true)
val pointerIcon =
PointerIcon.getLoadedSystemIcon(
@@ -108,11 +136,20 @@ class PointerIconLoadingTest {
assumeTrue(enableVectorCursors())
assumeTrue(enableVectorCursorA11ySettings())
+ val theme: Resources.Theme = context.getResources().newTheme()
+ theme.setTo(context.getTheme())
+ theme.applyStyle(
+ PointerIcon.vectorFillStyleToResource(PointerIcon.POINTER_ICON_VECTOR_STYLE_FILL_BLACK),
+ /* force= */ true)
+ theme.applyStyle(
+ PointerIcon.vectorStrokeStyleToResource(
+ PointerIcon.POINTER_ICON_VECTOR_STYLE_STROKE_WHITE),
+ /* force= */ true)
val pointerScale = 2f
val pointerIcon =
PointerIcon.getLoadedSystemIcon(
- context,
+ ContextThemeWrapper(context, theme),
PointerIcon.TYPE_ARROW,
/* useLargeIcons= */ false,
pointerScale)
@@ -129,8 +166,7 @@ class PointerIconLoadingTest {
const val SCREEN_WIDTH_DP = 480
const val SCREEN_HEIGHT_DP = 800
const val ASSETS_PATH = "tests/input/assets"
- val TEST_OUTPUT_PATH = Environment.getExternalStorageDirectory().absolutePath +
- "/InputTests/" +
- PointerIconLoadingTest::class.java.simpleName
+ val TEST_OUTPUT_PATH =
+ "/sdcard/Download/InputTests/" + PointerIconLoadingTest::class.java.simpleName
}
}
diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
new file mode 100644
index 000000000000..c61a25021949
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2024 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.test.input
+
+import android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY
+import android.app.Instrumentation
+import android.cts.input.EventVerifier
+import android.graphics.PointF
+import android.hardware.input.InputManager
+import android.os.ParcelFileDescriptor
+import android.util.Log
+import android.util.Size
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.input.BatchedEventSplitter
+import com.android.cts.input.InputJsonParser
+import com.android.cts.input.VirtualDisplayActivityScenario
+import com.android.cts.input.inputeventmatchers.isResampled
+import com.android.cts.input.inputeventmatchers.withButtonState
+import com.android.cts.input.inputeventmatchers.withHistorySize
+import com.android.cts.input.inputeventmatchers.withMotionAction
+import com.android.cts.input.inputeventmatchers.withPressure
+import com.android.cts.input.inputeventmatchers.withRawCoords
+import com.android.cts.input.inputeventmatchers.withSource
+import junit.framework.Assert.fail
+import org.hamcrest.Matchers.allOf
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Integration tests for the input pipeline that replays recording taken from physical input devices
+ * at the evdev interface level, and makes assertions on the events that are received by a test app.
+ *
+ * These tests associate the playback input device with a virtual display to make these tests
+ * agnostic to the device form factor.
+ *
+ * New recordings can be taken using the `evemu-record` shell command.
+ */
+@RunWith(Parameterized::class)
+class UinputRecordingIntegrationTests {
+
+ companion object {
+ /**
+ * Add new test cases by adding a new [TestData] to the following list.
+ */
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data(): Iterable<Any> =
+ listOf(
+ TestData(
+ "GooglePixelTabletTouchscreen", R.raw.google_pixel_tablet_touchscreen,
+ R.raw.google_pixel_tablet_touchscreen_events, Size(1600, 2560),
+ vendorId = 0x0603, productId = 0x7806
+ ),
+ )
+
+ /**
+ * Use the debug mode to see the JSON-encoded received events in logcat.
+ */
+ const val DEBUG_RECEIVED_EVENTS = false
+
+ const val INPUT_DEVICE_SOURCE_ALL = -1
+ val TAG = UinputRecordingIntegrationTests::class.java.simpleName
+ }
+
+ class TestData(
+ val name: String,
+ val uinputRecordingResource: Int,
+ val expectedEventsResource: Int,
+ val displaySize: Size,
+ val vendorId: Int,
+ val productId: Int,
+ ) {
+ override fun toString(): String = name
+ }
+
+ private lateinit var instrumentation: Instrumentation
+ private lateinit var parser: InputJsonParser
+
+ @get:Rule
+ val testName = TestName()
+
+ @Parameterized.Parameter(0)
+ lateinit var testData: TestData
+
+ @Before
+ fun setUp() {
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ parser = InputJsonParser(instrumentation.context)
+ }
+
+ @Test
+ fun testEvemuRecording() {
+ VirtualDisplayActivityScenario.AutoClose<CaptureEventActivity>(
+ testName,
+ size = testData.displaySize
+ ).use { scenario ->
+ scenario.activity.window.decorView.requestUnbufferedDispatch(INPUT_DEVICE_SOURCE_ALL)
+
+ try {
+ instrumentation.uiAutomation.adoptShellPermissionIdentity(
+ ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+ )
+
+ val inputPort = "uinput:1:${testData.vendorId}:${testData.productId}"
+ val inputManager =
+ instrumentation.context.getSystemService(InputManager::class.java)!!
+ try {
+ inputManager.addUniqueIdAssociationByPort(
+ inputPort,
+ scenario.virtualDisplay.display.uniqueId!!,
+ )
+
+ injectUinputEvents().use {
+ if (DEBUG_RECEIVED_EVENTS) {
+ printReceivedEventsToLogcat(scenario.activity)
+ fail("Test cannot pass in debug mode!")
+ }
+
+ val verifier = EventVerifier(
+ BatchedEventSplitter { scenario.activity.getInputEvent() }
+ )
+ verifyEvents(verifier)
+ scenario.activity.assertNoEvents()
+ }
+ } finally {
+ inputManager.removeUniqueIdAssociationByPort(inputPort)
+ }
+ } finally {
+ instrumentation.uiAutomation.dropShellPermissionIdentity()
+ }
+ }
+ }
+
+ private fun printReceivedEventsToLogcat(activity: CaptureEventActivity) {
+ val getNextEvent = BatchedEventSplitter { activity.getInputEvent() }
+ var receivedEvent: InputEvent? = getNextEvent()
+ while (receivedEvent != null) {
+ Log.d(TAG,
+ parser.encodeEvent(receivedEvent)?.toString()
+ ?: "(Failed to encode received event)"
+ )
+ receivedEvent = getNextEvent()
+ }
+ }
+
+ /**
+ * Plays back the evemu recording associated with the current test case by injecting it via
+ * the `uinput` shell command in interactive mode. The recording playback will begin
+ * immediately, and the shell command (and the associated input device) will remain alive
+ * until the returned [AutoCloseable] is closed.
+ */
+ private fun injectUinputEvents(): AutoCloseable {
+ val fds = instrumentation.uiAutomation!!.executeShellCommandRw("uinput -")
+ // We do not need to use stdout in this test.
+ fds[0].close()
+
+ return ParcelFileDescriptor.AutoCloseOutputStream(fds[1]).also { stdin ->
+ instrumentation.context.resources.openRawResource(
+ testData.uinputRecordingResource,
+ ).use { inputStream ->
+ stdin.write(inputStream.readBytes())
+
+ // TODO(b/367419268): Remove extra event injection when uinput parsing is fixed.
+ // Inject an extra sync event with an arbitrarily large timestamp, because the
+ // uinput command will not process the last event until either the next event is
+ // parsed, or fd is closed. Injecting this sync allows us complete injection of
+ // the evemu recording and extend the lifetime of the input device by keeping this
+ // fd open.
+ stdin.write("\nE: 9999.99 0 0 0\n".toByteArray())
+ stdin.flush()
+ }
+ }
+ }
+
+ private fun verifyEvents(verifier: EventVerifier) {
+ val uinputTestData = parser.getUinputTestData(testData.expectedEventsResource)
+ for (test in uinputTestData) {
+ for ((index, expectedEvent) in test.events.withIndex()) {
+ if (expectedEvent is MotionEvent) {
+ verifier.assertReceivedMotion(
+ allOf(
+ withMotionAction(expectedEvent.action),
+ withSource(expectedEvent.source),
+ withButtonState(expectedEvent.buttonState),
+ withRawCoords(PointF(expectedEvent.rawX, expectedEvent.rawY)),
+ withPressure(expectedEvent.pressure),
+ isResampled(false),
+ withHistorySize(0),
+ ),
+ "${test.name}: Expected event at index $index",
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index 63782f1bf955..1842f0a64a83 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+// InputMonitor is deprecated, but we still need to test it.
+@file:Suppress("DEPRECATION")
+
package com.android.test.input
import android.app.Activity
@@ -43,6 +46,7 @@ class UnresponsiveGestureMonitorActivity : Activity() {
}
private lateinit var mInputEventReceiver: InputEventReceiver
private lateinit var mInputMonitor: InputMonitor
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inputManager = checkNotNull(getSystemService(InputManager::class.java))
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index e60d8efdbfa4..a2c3572eca9b 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -37,14 +37,14 @@ import android.content.res.Configuration;
import android.os.SystemClock;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
import android.view.WindowManager;
import android.widget.EditText;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
import org.junit.Rule;
import org.junit.Test;
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 2ac25f2696d3..b994bfb00007 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -33,10 +33,10 @@ import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Intent;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
-import android.support.test.uiautomator.UiDevice;
import android.widget.EditText;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import org.junit.Rule;
import org.junit.Test;
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 5368025ff898..2128cbf90542 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -48,12 +48,12 @@ import android.os.Build;
import android.os.SystemClock;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
-import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import android.view.WindowManager;
import android.widget.EditText;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import org.junit.Rule;
import org.junit.Test;
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
index c7463218b646..1249a4564e8e 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
@@ -18,10 +18,10 @@ package com.android.inputmethod.stresstest;
import android.app.Instrumentation;
import android.os.RemoteException;
-import android.support.test.uiautomator.UiDevice;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index bb4e1ec9b6f2..9f35c7b7fa33 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -24,6 +24,7 @@ android_test {
"flickerlib-parsers",
"perfetto_trace_java_protos",
"flickerlib-trace_processor_shell",
+ "ravenwood-junit",
],
java_resource_dirs: ["res"],
certificate: "platform",
@@ -31,6 +32,27 @@ android_test {
test_suites: ["device-tests"],
}
+// Run just ApplicationSharedMemoryTest with ABI override for 32 bits.
+// This is to test that on systems that support multi-ABI,
+// ApplicationSharedMemory works in app processes launched with a different ABI
+// than that of the system processes.
+android_test {
+ name: "ApplicationSharedMemoryTest32",
+ team: "trendy_team_system_performance",
+ srcs: ["src/com/android/internal/os/ApplicationSharedMemoryTest.java"],
+ libs: ["android.test.runner.stubs.system"],
+ static_libs: [
+ "junit",
+ "androidx.test.rules",
+ "platform-test-annotations",
+ ],
+ manifest: "ApplicationSharedMemoryTest32/AndroidManifest.xml",
+ test_config: "ApplicationSharedMemoryTest32/AndroidTest.xml",
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
+
android_ravenwood_test {
name: "InternalTestsRavenwood",
static_libs: [
@@ -39,7 +61,14 @@ android_ravenwood_test {
"platform-test-annotations",
],
srcs: [
+ "src/com/android/internal/graphics/ColorUtilsTest.java",
"src/com/android/internal/util/ParcellingTests.java",
],
auto_gen_config: true,
}
+
+java_test_helper_library {
+ name: "ApplicationSharedMemoryTestRule",
+ srcs: ["src/com/android/internal/os/ApplicationSharedMemoryTestRule.java"],
+ static_libs: ["junit"],
+}
diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml
index 7b67e9ebcced..2d6c650eb2dc 100644
--- a/tests/Internal/AndroidTest.xml
+++ b/tests/Internal/AndroidTest.xml
@@ -26,4 +26,12 @@
<option name="package" value="com.android.internal.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.internal.tests/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
</configuration> \ No newline at end of file
diff --git a/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml b/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml
new file mode 100644
index 000000000000..4e1058ead734
--- /dev/null
+++ b/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.tests">
+ <application
+ android:use32bitAbi="true"
+ android:multiArch="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.internal.tests"
+ android:label="Internal Tests"/>
+</manifest>
diff --git a/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml b/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml
new file mode 100644
index 000000000000..9bde8b7522e3
--- /dev/null
+++ b/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<configuration description="Runs tests for internal classes/utilities.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ApplicationSharedMemoryTest32.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="InternalTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.internal.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path"/>
+ <option name="directory-keys"
+ value="/data/user/0/com.android.internal.tests/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration> \ No newline at end of file
diff --git a/tests/Internal/ApplicationSharedMemoryTest32/OWNERS b/tests/Internal/ApplicationSharedMemoryTest32/OWNERS
new file mode 100644
index 000000000000..1ff3fac8ae6f
--- /dev/null
+++ b/tests/Internal/ApplicationSharedMemoryTest32/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/PERFORMANCE_OWNERS \ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
index d0bb8e3745bc..38a22f2fc2f3 100644
--- a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
+++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
@@ -19,14 +19,19 @@ package com.android.internal.graphics;
import static org.junit.Assert.assertTrue;
import android.graphics.Color;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
@SmallTest
public class ColorUtilsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
@Test
public void calculateMinimumBackgroundAlpha_satisfiestContrast() {
diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
new file mode 100644
index 000000000000..d03ad5cb2877
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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.internal.os;
+
+import java.io.IOException;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.Before;
+
+import java.io.FileDescriptor;
+
+/** Tests for {@link TimeoutRecord}. */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ApplicationSharedMemoryTest {
+
+ @Before
+ public void setUp() {
+ // Skip tests if the feature under test is disabled.
+ assumeTrue(Flags.applicationSharedMemoryEnabled());
+ }
+
+ /**
+ * Every application process, including ours, should have had an instance installed at this
+ * point.
+ */
+ @Test
+ public void hasInstance() {
+ // This shouldn't throw and shouldn't return null.
+ assertNotNull(ApplicationSharedMemory.getInstance());
+ }
+
+ /** Any app process should be able to read shared memory values. */
+ @Test
+ public void canRead() {
+ ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance();
+ try {
+ instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
+ // Don't actually care about the value of the above.
+ } catch (java.time.DateTimeException e) {
+ // This exception is okay during testing. It means there was no time source, which
+ // could be because of network problems or a feature being flagged off.
+ }
+ }
+
+ /** Application processes should not have mutable access. */
+ @Test
+ public void appInstanceNotMutable() {
+ ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance();
+ try {
+ instance.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17);
+ fail("Attempted mutation in an app process should throw");
+ } catch (Exception expected) {
+ }
+ }
+
+ /** Instances share memory if they share the underlying memory region. */
+ @Test
+ public void instancesShareMemory() throws IOException {
+ ApplicationSharedMemory instance1 = ApplicationSharedMemory.create();
+ ApplicationSharedMemory instance2 =
+ ApplicationSharedMemory.fromFileDescriptor(
+ instance1.getFileDescriptor(), /* mutable= */ true);
+ ApplicationSharedMemory instance3 =
+ ApplicationSharedMemory.fromFileDescriptor(
+ instance2.getReadOnlyFileDescriptor(), /* mutable= */ false);
+
+ instance1.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17);
+ assertEquals(
+ 17, instance1.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis());
+ assertEquals(
+ 17, instance2.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis());
+ assertEquals(
+ 17, instance3.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis());
+
+ instance2.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(24);
+ assertEquals(
+ 24, instance1.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis());
+ assertEquals(
+ 24, instance2.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis());
+ assertEquals(
+ 24, instance3.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis());
+ }
+
+ /** Can't map read-only memory as mutable. */
+ @Test
+ public void readOnlyCantBeMutable() throws IOException {
+ ApplicationSharedMemory readWriteInstance = ApplicationSharedMemory.create();
+ FileDescriptor readOnlyFileDescriptor = readWriteInstance.getReadOnlyFileDescriptor();
+
+ try {
+ ApplicationSharedMemory.fromFileDescriptor(readOnlyFileDescriptor, /* mutable= */ true);
+ fail("Shouldn't be able to map read-only memory as mutable");
+ } catch (Exception expected) {
+ }
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java
new file mode 100644
index 000000000000..ff2a4611cdf0
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 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.internal.os;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import com.android.internal.os.ApplicationSharedMemory;
+
+/** Test rule that sets up and tears down ApplicationSharedMemory for test. */
+public class ApplicationSharedMemoryTestRule implements TestRule {
+
+ private ApplicationSharedMemory mSavedInstance;
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ setup();
+ try {
+ base.evaluate(); // Run the test
+ } finally {
+ teardown();
+ }
+ }
+ };
+ }
+
+ private void setup() {
+ mSavedInstance = ApplicationSharedMemory.sInstance;
+ ApplicationSharedMemory.sInstance = ApplicationSharedMemory.create();
+ }
+
+ private void teardown() {
+ ApplicationSharedMemory.sInstance.close();
+ ApplicationSharedMemory.sInstance = mSavedInstance;
+ mSavedInstance = null;
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
index 65a3436a4c5e..fb63422cdf9f 100644
--- a/tests/Internal/src/com/android/internal/util/ParcellingTests.java
+++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
@@ -18,6 +18,7 @@ package com.android.internal.util;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -26,6 +27,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.Parcelling.BuiltIn.ForInstant;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -38,6 +40,9 @@ import java.time.Instant;
@RunWith(JUnit4.class)
public class ParcellingTests {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private Parcel mParcel = Parcel.obtain();
@Test
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index c0e90f9232d6..8d143b69d124 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -727,7 +727,17 @@ public class CrashRecoveryTest {
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
-
+ try {
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
+ final PackageInfo res = new PackageInfo();
+ res.packageName = inv.getArgument(0);
+ res.setApexPackageName(res.packageName);
+ res.setLongVersionCode(VERSION_CODE);
+ return res;
+ });
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
watchdog.registerHealthObserver(rollbackObserver);
return rollbackObserver;
}
@@ -787,8 +797,10 @@ public class CrashRecoveryTest {
// Verify controller by default is started when packages are ready
assertThat(controller.mIsEnabled).isTrue();
- verify(mConnectivityModuleConnector).registerHealthListener(
- mConnectivityModuleCallbackCaptor.capture());
+ if (!Flags.refactorCrashrecovery()) {
+ verify(mConnectivityModuleConnector).registerHealthListener(
+ mConnectivityModuleCallbackCaptor.capture());
+ }
}
mAllocatedWatchdogs.add(watchdog);
return watchdog;
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index a8b383cd4274..0364781ab064 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -23,9 +23,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -33,6 +36,7 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
@@ -42,6 +46,9 @@ import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.test.TestLooper;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
@@ -107,6 +114,9 @@ public class PackageWatchdogTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
private Context mSpyContext;
@@ -116,6 +126,7 @@ public class PackageWatchdogTest {
private ConnectivityModuleConnector mConnectivityModuleConnector;
@Mock
private PackageManager mMockPackageManager;
+ @Mock Intent mMockIntent;
// Mock only sysprop apis
private PackageWatchdog.BootThreshold mSpyBootThreshold;
@Captor
@@ -961,6 +972,7 @@ public class PackageWatchdogTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY)
public void testNetworkStackFailure() {
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
final PackageWatchdog wd = createWatchdog();
@@ -981,6 +993,7 @@ public class PackageWatchdogTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY)
public void testNetworkStackFailureRecoverabilityDetection() {
final PackageWatchdog wd = createWatchdog();
@@ -1669,6 +1682,19 @@ public class PackageWatchdogTest {
PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS);
}
+ /**
+ * Tests device config changes are propagated correctly.
+ */
+ @Test
+ public void testRegisterShutdownBroadcastReceiver() {
+ PackageWatchdog watchdog = createWatchdog();
+ doReturn(mMockIntent).when(mSpyContext)
+ .registerReceiverForAllUsers(any(), any(), any(), any());
+
+ watchdog.registerShutdownBroadcastReceiver();
+ verify(mSpyContext).registerReceiverForAllUsers(any(), any(), eq(null), eq(null));
+ }
+
private void adoptShellPermissions(String... permissions) {
InstrumentationRegistry
.getInstrumentation()
@@ -1740,8 +1766,10 @@ public class PackageWatchdogTest {
// Verify controller by default is started when packages are ready
assertThat(controller.mIsEnabled).isTrue();
- verify(mConnectivityModuleConnector).registerHealthListener(
- mConnectivityModuleCallbackCaptor.capture());
+ if (!Flags.refactorCrashrecovery()) {
+ verify(mConnectivityModuleConnector).registerHealthListener(
+ mConnectivityModuleCallbackCaptor.capture());
+ }
}
mAllocatedWatchdogs.add(watchdog);
return watchdog;
@@ -1849,7 +1877,7 @@ public class PackageWatchdogTest {
return true;
}
- public String getName() {
+ public String getUniqueIdentifier() {
return mName;
}
diff --git a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
index 8c16079dca85..01f8bc148fce 100644
--- a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
+++ b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
@@ -16,33 +16,26 @@
package com.android.tests.rollback.host;
+import static com.google.common.truth.Truth.assertThat;
+
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Truth;
-import static com.google.common.truth.Truth.assertThat;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class WatchdogEventLogger {
- private static final String[] ROLLBACK_EVENT_TYPES = {
- "ROLLBACK_INITIATE", "ROLLBACK_BOOT_TRIGGERED", "ROLLBACK_SUCCESS"};
- private static final String[] ROLLBACK_EVENT_ATTRS = {
- "logPackage", "rollbackReason", "failedPackageName"};
- private static final String PROP_PREFIX = "persist.sys.rollbacktest.";
private ITestDevice mDevice;
- private void resetProperties(boolean enabled) throws Exception {
+ private void updateTestSysProp(boolean enabled) throws Exception {
try {
mDevice.enableAdbRoot();
assertThat(mDevice.setProperty(
- PROP_PREFIX + "enabled", String.valueOf(enabled))).isTrue();
- for (String type : ROLLBACK_EVENT_TYPES) {
- String key = PROP_PREFIX + type;
- assertThat(mDevice.setProperty(key, "")).isTrue();
- for (String attr : ROLLBACK_EVENT_ATTRS) {
- assertThat(mDevice.setProperty(key + "." + attr, "")).isTrue();
- }
- }
+ "persist.sys.rollbacktest.enabled", String.valueOf(enabled))).isTrue();
} finally {
mDevice.disableAdbRoot();
}
@@ -50,19 +43,17 @@ public class WatchdogEventLogger {
public void start(ITestDevice device) throws Exception {
mDevice = device;
- resetProperties(true);
+ updateTestSysProp(true);
}
public void stop() throws Exception {
if (mDevice != null) {
- resetProperties(false);
+ updateTestSysProp(false);
}
}
- private boolean matchProperty(String type, String attr, String expectedVal) throws Exception {
- String key = PROP_PREFIX + type + "." + attr;
- String val = mDevice.getProperty(key);
- return expectedVal == null || expectedVal.equals(val);
+ private boolean verifyEventContainsVal(String watchdogEvent, String expectedVal) {
+ return expectedVal == null || watchdogEvent.contains(expectedVal);
}
/**
@@ -72,11 +63,33 @@ public class WatchdogEventLogger {
* occurred, and return {@code true} if an event exists which matches all criteria.
*/
public boolean watchdogEventOccurred(String type, String logPackage,
- String rollbackReason, String failedPackageName) throws Exception {
- return mDevice.getBooleanProperty(PROP_PREFIX + type, false)
- && matchProperty(type, "logPackage", logPackage)
- && matchProperty(type, "rollbackReason", rollbackReason)
- && matchProperty(type, "failedPackageName", failedPackageName);
+ String rollbackReason, String failedPackageName) {
+ String watchdogEvent = getEventForRollbackType(type);
+ return (watchdogEvent != null)
+ && verifyEventContainsVal(watchdogEvent, logPackage)
+ && verifyEventContainsVal(watchdogEvent, rollbackReason)
+ && verifyEventContainsVal(watchdogEvent, failedPackageName);
+ }
+
+ /** Returns last matched event for rollbackType **/
+ private String getEventForRollbackType(String rollbackType) {
+ String lastMatchedEvent = null;
+ try {
+ String rollbackDump = mDevice.executeShellCommand("dumpsys rollback");
+ String eventRegex = ".*%s%s(.*)\\n";
+ String eventPrefix = "Watchdog event occurred with type: ";
+
+ final Pattern pattern = Pattern.compile(
+ String.format(eventRegex, eventPrefix, rollbackType));
+ final Matcher matcher = pattern.matcher(rollbackDump);
+ while (matcher.find()) {
+ lastMatchedEvent = matcher.group(1);
+ }
+ CLog.d("Found watchdogEvent: " + lastMatchedEvent + " for type: " + rollbackType);
+ } catch (Exception e) {
+ CLog.e("Unable to find event for type: " + rollbackType, e);
+ }
+ return lastMatchedEvent;
}
static class Subject extends com.google.common.truth.Subject {
@@ -97,7 +110,7 @@ public class WatchdogEventLogger {
}
void eventOccurred(String type, String logPackage, String rollbackReason,
- String failedPackageName) throws Exception {
+ String failedPackageName) {
check("watchdogEventOccurred(type=%s, logPackage=%s, rollbackReason=%s, "
+ "failedPackageName=%s)", type, logPackage, rollbackReason, failedPackageName)
.that(mActual.watchdogEventOccurred(type, logPackage, rollbackReason,
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
index 359eb35384c7..5012c235a2a5 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -84,6 +84,7 @@ public class SurfaceControlViewHostSyncTest extends Activity implements SurfaceH
content.addView(enableSyncButton,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM));
+ content.setFitsSystemWindows(true);
setContentView(content);
mSv.setZOrderOnTop(false);
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
index 73e01634709e..4119ea2c73c3 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
@@ -37,6 +37,7 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde
protected void onCreate(Bundle savedInstanceState) {
FrameLayout content = new FrameLayout(this);
+ content.setFitsSystemWindows(true);
super.onCreate(savedInstanceState);
mView = new SurfaceView(this);
content.addView(mView, new FrameLayout.LayoutParams(
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
index ac7dc9e2f31f..528706860b31 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -88,6 +88,7 @@ public class SurfaceInputTestActivity extends Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout content = new LinearLayout(this);
+ content.setFitsSystemWindows(true);
mLocalSurfaceView = new SurfaceView(this);
content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index 5058331187e8..fa352cf1e832 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,7 +18,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
</resources>
diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp
index f5c1ae57e8c2..90998e67ae31 100644
--- a/tests/Tracing/Android.bp
+++ b/tests/Tracing/Android.bp
@@ -15,7 +15,7 @@ android_test {
},
// Include some source files directly to be able to access package members
srcs: ["src/**/*.java"],
- libs: ["android.test.runner.impl"],
+ libs: ["android.test.runner.stubs.system"],
static_libs: [
"junit",
"androidx.test.rules",
diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING
index 7f58fceee24d..f6e5221b721b 100644
--- a/tests/Tracing/TEST_MAPPING
+++ b/tests/Tracing/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "TracingTests"
}
diff --git a/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java b/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java
new file mode 100644
index 000000000000..bbeb18dfbecd
--- /dev/null
+++ b/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import static android.internal.perfetto.protos.TestEventOuterClass.TestEvent.PAYLOAD;
+import static android.internal.perfetto.protos.TestEventOuterClass.TestEvent.TestPayload.SINGLE_INT;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.FOR_TESTING;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
+import android.internal.perfetto.protos.TestConfigOuterClass.TestConfig;
+import android.tools.ScenarioBuilder;
+import android.tools.Tag;
+import android.tools.io.TraceType;
+import android.tools.traces.TraceConfig;
+import android.tools.traces.TraceConfigs;
+import android.tools.traces.io.ResultReader;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+import android.tools.traces.monitors.TraceMonitor;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import perfetto.protos.PerfettoConfig;
+import perfetto.protos.TracePacketOuterClass;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class DataSourceTest {
+ private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ private static TestDataSource sTestDataSource;
+
+ private static TestDataSource.DataSourceInstanceProvider sInstanceProvider;
+ private static TestDataSource.TlsStateProvider sTlsStateProvider;
+ private static TestDataSource.IncrementalStateProvider sIncrementalStateProvider;
+
+ public DataSourceTest() throws IOException {}
+
+ @BeforeClass
+ public static void beforeAll() {
+ Producer.init(InitArguments.DEFAULTS);
+ setupProviders();
+ sTestDataSource = new TestDataSource(
+ (ds, idx, configStream) -> sInstanceProvider.provide(ds, idx, configStream),
+ args -> sTlsStateProvider.provide(args),
+ args -> sIncrementalStateProvider.provide(args));
+ sTestDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ private static void setupProviders() {
+ sInstanceProvider = (ds, idx, configStream) ->
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ sTlsStateProvider = args -> new TestDataSource.TestTlsState();
+ sIncrementalStateProvider = args -> new TestDataSource.TestIncrementalState();
+ }
+
+ @Before
+ public void setup() {
+ setupProviders();
+ }
+
+ @Test
+ public void canTraceData() throws InvalidProtocolBufferException {
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, 10);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt() == 10).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canUseTlsStateForCustomState() {
+ final int expectedStateTestValue = 10;
+ final AtomicInteger actualStateTestValue = new AtomicInteger();
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = expectedStateTestValue;
+ });
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ actualStateTestValue.set(state.testStateValue);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(actualStateTestValue.get()).isEqualTo(expectedStateTestValue);
+ }
+
+ @Test
+ public void eachInstanceHasOwnTlsState() {
+ final int[] expectedStateTestValues = new int[] { 1, 2 };
+ final int[] actualStateTestValues = new int[] { 0, 0 };
+
+ final TraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+ final TraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor1.start();
+ try {
+ traceMonitor2.start();
+
+ AtomicInteger index = new AtomicInteger(0);
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = expectedStateTestValues[index.getAndIncrement()];
+ });
+
+ index.set(0);
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ actualStateTestValues[index.getAndIncrement()] = state.testStateValue;
+ });
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(actualStateTestValues[0]).isEqualTo(expectedStateTestValues[0]);
+ Truth.assertThat(actualStateTestValues[1]).isEqualTo(expectedStateTestValues[1]);
+ }
+
+ @Test
+ public void eachThreadHasOwnTlsState() throws InterruptedException {
+ final int thread1ExpectedStateValue = 1;
+ final int thread2ExpectedStateValue = 2;
+
+ final AtomicInteger thread1ActualStateValue = new AtomicInteger();
+ final AtomicInteger thread2ActualStateValue = new AtomicInteger();
+
+ final CountDownLatch setUpLatch = new CountDownLatch(2);
+ final CountDownLatch setStateLatch = new CountDownLatch(2);
+ final CountDownLatch setOutStateLatch = new CountDownLatch(2);
+
+ final RunnableCreator createTask = (stateValue, stateOut) -> () -> {
+ Producer.init(InitArguments.DEFAULTS);
+
+ setUpLatch.countDown();
+
+ try {
+ setUpLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ sTestDataSource.trace((ctx) -> {
+ TestDataSource.TestTlsState state = ctx.getCustomTlsState();
+ state.testStateValue = stateValue;
+ setStateLatch.countDown();
+ });
+
+ try {
+ setStateLatch.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ sTestDataSource.trace((ctx) -> {
+ stateOut.set(ctx.getCustomTlsState().testStateValue);
+ setOutStateLatch.countDown();
+ });
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+
+ new Thread(
+ createTask.create(thread1ExpectedStateValue, thread1ActualStateValue)).start();
+ new Thread(
+ createTask.create(thread2ExpectedStateValue, thread2ActualStateValue)).start();
+
+ setOutStateLatch.await(3, TimeUnit.SECONDS);
+
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(thread1ActualStateValue.get()).isEqualTo(thread1ExpectedStateValue);
+ Truth.assertThat(thread2ActualStateValue.get()).isEqualTo(thread2ExpectedStateValue);
+ }
+
+ @Test
+ public void incrementalStateIsReset() throws InterruptedException {
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build())
+ .setIncrementalTimeout(10)
+ .build();
+
+ final AtomicInteger testStateValue = new AtomicInteger();
+ try {
+ traceMonitor.start();
+
+ sTestDataSource.trace(ctx -> ctx.getIncrementalState().testStateValue = 1);
+
+ // Timeout to make sure the incremental state is cleared.
+ Thread.sleep(1000);
+
+ sTestDataSource.trace(ctx ->
+ testStateValue.set(ctx.getIncrementalState().testStateValue));
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ Truth.assertThat(testStateValue.get()).isNotEqualTo(1);
+ }
+
+ @Test
+ public void getInstanceConfigOnCreateInstance() throws IOException {
+ final int expectedDummyIntValue = 10;
+ AtomicReference<ProtoInputStream> configStream = new AtomicReference<>();
+ sInstanceProvider = (ds, idx, config) -> {
+ configStream.set(config);
+ return new TestDataSource.TestDataSourceInstance(ds, idx);
+ };
+
+ final TraceMonitor monitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name)
+ .setForTesting(PerfettoConfig.TestConfig.newBuilder().setDummyFields(
+ PerfettoConfig.TestConfig.DummyFields.newBuilder()
+ .setFieldInt32(expectedDummyIntValue)
+ .build())
+ .build())
+ .build())
+ .build();
+
+ try {
+ monitor.start();
+ } finally {
+ monitor.stop(mWriter);
+ }
+
+ int configDummyIntValue = 0;
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) DataSourceConfig.FOR_TESTING) {
+ final long forTestingToken = configStream.get()
+ .start(DataSourceConfig.FOR_TESTING);
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) TestConfig.DUMMY_FIELDS) {
+ final long dummyFieldsToken = configStream.get()
+ .start(TestConfig.DUMMY_FIELDS);
+ while (configStream.get().nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (configStream.get().getFieldNumber()
+ == (int) TestConfig.DummyFields.FIELD_INT32) {
+ int val = configStream.get().readInt(
+ TestConfig.DummyFields.FIELD_INT32);
+ if (val != 0) {
+ configDummyIntValue = val;
+ break;
+ }
+ }
+ }
+ configStream.get().end(dummyFieldsToken);
+ break;
+ }
+ }
+ configStream.get().end(forTestingToken);
+ break;
+ }
+ }
+
+ Truth.assertThat(configDummyIntValue).isEqualTo(expectedDummyIntValue);
+ }
+
+ @Test
+ public void multipleTraceInstances() throws IOException, InterruptedException {
+ final int instanceCount = 3;
+
+ final List<TraceMonitor> monitors = new ArrayList<>();
+ final List<ResultWriter> writers = new ArrayList<>();
+
+ for (int i = 0; i < instanceCount; i++) {
+ final ResultWriter writer = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+ writers.add(writer);
+ }
+
+ // Start at 1 because 0 is considered null value so payload will be ignored in that case
+ TestDataSource.TestTlsState.lastIndex = 1;
+
+ final AtomicInteger traceCallCount = new AtomicInteger();
+ final CountDownLatch latch = new CountDownLatch(instanceCount);
+
+ try {
+ // Start instances
+ for (int i = 0; i < instanceCount; i++) {
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+ monitors.add(traceMonitor);
+ traceMonitor.start();
+ }
+
+ // Trace the stateIndex of the tracing instance.
+ sTestDataSource.trace(ctx -> {
+ final int testIntValue = ctx.getCustomTlsState().stateIndex;
+ traceCallCount.incrementAndGet();
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ long forTestingToken = os.start(FOR_TESTING);
+ long payloadToken = os.start(PAYLOAD);
+ os.write(SINGLE_INT, testIntValue);
+ os.end(payloadToken);
+ os.end(forTestingToken);
+
+ latch.countDown();
+ });
+ } finally {
+ // Stop instances
+ for (int i = 0; i < instanceCount; i++) {
+ final TraceMonitor monitor = monitors.get(i);
+ final ResultWriter writer = writers.get(i);
+ monitor.stop(writer);
+ }
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(traceCallCount.get()).isEqualTo(instanceCount);
+
+ for (int i = 0; i < instanceCount; i++) {
+ final int expectedTracedValue = i + 1;
+ final ResultWriter writer = writers.get(i);
+ final ResultReader reader = new ResultReader(writer.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace =
+ perfetto.protos.TraceOuterClass.Trace.parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ Truth.assertWithMessage("One packet has for testing data")
+ .that(tracePackets).hasSize(1);
+
+ final List<TracePacketOuterClass.TracePacket> matchingPackets =
+ tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload()
+ .getSingleInt() == expectedTracedValue).toList();
+ Truth.assertWithMessage(
+ "One packet has testing data with a payload with the expected value")
+ .that(matchingPackets).hasSize(1);
+ }
+ }
+
+ @Test
+ public void onStartCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) -> new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ },
+ (args) -> {},
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ try {
+ traceMonitor.start();
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+ }
+
+ @Test
+ public void onFlushCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ },
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, 10);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ public void onStopCallbackTriggered() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean callbackCalled = new AtomicBoolean(false);
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> {},
+ (args) -> {
+ callbackCalled.set(true);
+ latch.countDown();
+ }
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ Truth.assertThat(callbackCalled.get()).isFalse();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ latch.await(3, TimeUnit.SECONDS);
+ Truth.assertThat(callbackCalled.get()).isTrue();
+ }
+
+ @Test
+ public void canUseDataSourceInstanceToCreateTlsState() throws InvalidProtocolBufferException {
+ final Object testObject = new Object();
+
+ sInstanceProvider = (ds, idx, configStream) -> {
+ final TestDataSource.TestDataSourceInstance dsInstance =
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ dsInstance.testObject = testObject;
+ return dsInstance;
+ };
+
+ sTlsStateProvider = args -> {
+ final TestDataSource.TestTlsState tlsState = new TestDataSource.TestTlsState();
+
+ try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+ args.getDataSourceInstanceLocked()) {
+ if (dataSourceInstance != null) {
+ tlsState.testStateValue = dataSourceInstance.testObject.hashCode();
+ }
+ }
+
+ return tlsState;
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, ctx.getCustomTlsState().testStateValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == testObject.hashCode()).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canUseDataSourceInstanceToCreateIncrementalState()
+ throws InvalidProtocolBufferException {
+ final Object testObject = new Object();
+
+ sInstanceProvider = (ds, idx, configStream) -> {
+ final TestDataSource.TestDataSourceInstance dsInstance =
+ new TestDataSource.TestDataSourceInstance(ds, idx);
+ dsInstance.testObject = testObject;
+ return dsInstance;
+ };
+
+ sIncrementalStateProvider = args -> {
+ final TestDataSource.TestIncrementalState incrementalState =
+ new TestDataSource.TestIncrementalState();
+
+ try (TestDataSource.TestDataSourceInstance dataSourceInstance =
+ args.getDataSourceInstanceLocked()) {
+ if (dataSourceInstance != null) {
+ incrementalState.testStateValue = dataSourceInstance.testObject.hashCode();
+ }
+ }
+
+ return incrementalState;
+ };
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ sTestDataSource.trace((ctx) -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, ctx.getIncrementalState().testStateValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ });
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == testObject.hashCode()).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ @Test
+ public void canTraceOnFlush() throws InvalidProtocolBufferException, InterruptedException {
+ final int singleIntValue = 101;
+ sInstanceProvider = (ds, idx, config) ->
+ new TestDataSource.TestDataSourceInstance(
+ ds,
+ idx,
+ (args) -> {},
+ (args) -> sTestDataSource.trace(ctx -> {
+ final ProtoOutputStream protoOutputStream = ctx.newTracePacket();
+ long forTestingToken = protoOutputStream.start(FOR_TESTING);
+ long payloadToken = protoOutputStream.start(PAYLOAD);
+ protoOutputStream.write(SINGLE_INT, singleIntValue);
+ protoOutputStream.end(payloadToken);
+ protoOutputStream.end(forTestingToken);
+ }),
+ (args) -> {}
+ );
+
+ final TraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableCustomTrace(PerfettoConfig.DataSourceConfig.newBuilder()
+ .setName(sTestDataSource.name).build()).build();
+
+ try {
+ traceMonitor.start();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] rawProtoFromFile = reader.readBytes(TraceType.PERFETTO, Tag.ALL);
+ assert rawProtoFromFile != null;
+ final perfetto.protos.TraceOuterClass.Trace trace = perfetto.protos.TraceOuterClass.Trace
+ .parseFrom(rawProtoFromFile);
+
+ Truth.assertThat(trace.getPacketCount()).isGreaterThan(0);
+ final List<TracePacketOuterClass.TracePacket> tracePackets = trace.getPacketList()
+ .stream().filter(TracePacketOuterClass.TracePacket::hasForTesting).toList();
+ final List<TracePacketOuterClass.TracePacket> matchingPackets = tracePackets.stream()
+ .filter(it -> it.getForTesting().getPayload().getSingleInt()
+ == singleIntValue).toList();
+ Truth.assertThat(matchingPackets).hasSize(1);
+ }
+
+ interface RunnableCreator {
+ Runnable create(int state, AtomicInteger stateOut);
+ }
+}
diff --git a/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java b/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java
new file mode 100644
index 000000000000..d78f78b1cb0e
--- /dev/null
+++ b/tests/Tracing/src/android/tracing/perfetto/TestDataSource.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 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 android.tracing.perfetto;
+
+import android.util.proto.ProtoInputStream;
+
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class TestDataSource extends DataSource<TestDataSource.TestDataSourceInstance,
+ TestDataSource.TestTlsState, TestDataSource.TestIncrementalState> {
+ private final DataSourceInstanceProvider mDataSourceInstanceProvider;
+ private final TlsStateProvider mTlsStateProvider;
+ private final IncrementalStateProvider mIncrementalStateProvider;
+
+ interface DataSourceInstanceProvider {
+ TestDataSourceInstance provide(
+ TestDataSource dataSource, int instanceIndex, ProtoInputStream configStream);
+ }
+
+ interface TlsStateProvider {
+ TestTlsState provide(CreateTlsStateArgs<TestDataSourceInstance> args);
+ }
+
+ interface IncrementalStateProvider {
+ TestIncrementalState provide(CreateIncrementalStateArgs<TestDataSourceInstance> args);
+ }
+
+ public TestDataSource() {
+ this((ds, idx, config) -> new TestDataSourceInstance(ds, idx),
+ args -> new TestTlsState(), args -> new TestIncrementalState());
+ }
+
+ public TestDataSource(
+ DataSourceInstanceProvider dataSourceInstanceProvider,
+ TlsStateProvider tlsStateProvider,
+ IncrementalStateProvider incrementalStateProvider
+ ) {
+ super("android.tracing.perfetto.TestDataSource#" + UUID.randomUUID().toString());
+ this.mDataSourceInstanceProvider = dataSourceInstanceProvider;
+ this.mTlsStateProvider = tlsStateProvider;
+ this.mIncrementalStateProvider = incrementalStateProvider;
+ }
+
+ @Override
+ public TestDataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
+ return mDataSourceInstanceProvider.provide(this, instanceIndex, configStream);
+ }
+
+ @Override
+ public TestTlsState createTlsState(CreateTlsStateArgs args) {
+ return mTlsStateProvider.provide(args);
+ }
+
+ @Override
+ public TestIncrementalState createIncrementalState(CreateIncrementalStateArgs args) {
+ return mIncrementalStateProvider.provide(args);
+ }
+
+ public static class TestTlsState {
+ public int testStateValue;
+ public int stateIndex = lastIndex++;
+
+ public static int lastIndex = 0;
+ }
+
+ public static class TestIncrementalState {
+ public int testStateValue;
+ }
+
+ public static class TestDataSourceInstance extends DataSourceInstance {
+ public Object testObject;
+ Consumer<StartCallbackArguments> mStartCallback;
+ Consumer<FlushCallbackArguments> mFlushCallback;
+ Consumer<StopCallbackArguments> mStopCallback;
+
+ public TestDataSourceInstance(DataSource dataSource, int instanceIndex) {
+ this(dataSource, instanceIndex, args -> {}, args -> {}, args -> {});
+ }
+
+ public TestDataSourceInstance(
+ DataSource dataSource,
+ int instanceIndex,
+ Consumer<StartCallbackArguments> startCallback,
+ Consumer<FlushCallbackArguments> flushCallback,
+ Consumer<StopCallbackArguments> stopCallback) {
+ super(dataSource, instanceIndex);
+ this.mStartCallback = startCallback;
+ this.mFlushCallback = flushCallback;
+ this.mStopCallback = stopCallback;
+ }
+
+ @Override
+ public void onStart(StartCallbackArguments args) {
+ this.mStartCallback.accept(args);
+ }
+
+ @Override
+ public void onFlush(FlushCallbackArguments args) {
+ this.mFlushCallback.accept(args);
+ }
+
+ @Override
+ public void onStop(StopCallbackArguments args) {
+ this.mStopCallback.accept(args);
+ }
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index e9ff691151fe..8913e8c1996e 100644
--- a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,7 +59,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
-import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -90,7 +89,7 @@ public class LegacyProtoLogImplTest {
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {});
+ 1024 * 1024, mReader, 1024, () -> {});
}
@After
@@ -142,7 +141,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -159,7 +158,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -176,7 +175,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -191,7 +190,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -207,13 +206,20 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
verify(mReader, never()).getViewerString(anyLong());
}
+ @Test
+ public void loadViewerConfigOnLogcatGroupRegistration() {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ mProtoLog.registerGroups(TestProtoLogGroup.TEST_GROUP);
+ verify(mReader).loadViewerConfig(any(), any());
+ }
+
private static class ProtoLogData {
Long mMessageHash = null;
Long mElapsedTime = null;
@@ -276,7 +282,7 @@ public class LegacyProtoLogImplTest {
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b1110101001010100, null,
+ 0b1110101001010100,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -301,7 +307,7 @@ public class LegacyProtoLogImplTest {
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b01100100, null,
+ 0b01100100,
new Object[]{"test", 1, 0.1, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -325,7 +331,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
mProtoLog.startProtoLog(mock(PrintWriter.class));
mProtoLog.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b11, null, new Object[]{true});
+ 0b11, new Object[]{true});
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
try (InputStream is = new FileInputStream(mFile)) {
ProtoInputStream ip = new ProtoInputStream(is);
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 1d7b6b348e10..2692e12c57ed 100644
--- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -16,7 +16,7 @@
package com.android.internal.protolog;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static android.tools.traces.Utils.busyWaitForDataSourceRegistration;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -29,9 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.io.File.createTempFile;
-import static java.nio.file.Files.createTempDirectory;
-import android.content.Context;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.tools.ScenarioBuilder;
@@ -42,10 +40,10 @@ import android.tools.traces.io.ResultWriter;
import android.tools.traces.monitors.PerfettoTraceMonitor;
import android.tools.traces.protolog.ProtoLogTrace;
import android.tracing.perfetto.DataSource;
-import android.util.proto.ProtoInputStream;
-import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogDataType;
import com.android.internal.protolog.common.LogLevel;
@@ -54,31 +52,32 @@ import com.google.common.truth.Truth;
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Random;
-import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
-import perfetto.protos.Protolog;
-import perfetto.protos.ProtologCommon;
-
/**
* Test class for {@link ProtoLogImpl}.
*/
@SuppressWarnings("ConstantConditions")
-@SmallTest
@Presubmit
@RunWith(JUnit4.class)
-public class PerfettoProtoLogImplTest {
- private final File mTracingDirectory = createTempDirectory("temp").toFile();
+public class ProcessedPerfettoProtoLogImplTest {
+ private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog";
+ private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb";
+ private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext().getFilesDir();
private final ResultWriter mWriter = new ResultWriter()
.forScenario(new ScenarioBuilder()
@@ -93,25 +92,19 @@ public class PerfettoProtoLogImplTest {
new TraceConfig(false, true, false)
);
- private PerfettoProtoLogImpl mProtoLog;
- private Protolog.ProtoLogViewerConfig.Builder mViewerConfigBuilder;
- private File mFile;
- private Runnable mCacheUpdater;
+ private static ProtoLogConfigurationService sProtoLogConfigurationService;
+ private static PerfettoProtoLogImpl sProtoLog;
+ private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder;
+ private static Runnable sCacheUpdater;
- private ProtoLogViewerConfigReader mReader;
+ private static ProtoLogViewerConfigReader sReader;
- public PerfettoProtoLogImplTest() throws IOException {
+ public ProcessedPerfettoProtoLogImplTest() throws IOException {
}
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- final Context testContext = getInstrumentation().getContext();
- mFile = testContext.getFileStreamPath("tracing_test.dat");
- //noinspection ResultOfMethodCallIgnored
- mFile.delete();
-
- mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
+ @BeforeClass
+ public static void setUp() throws Exception {
+ sViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
.addGroups(
Protolog.ProtoLogViewerConfig.Group.newBuilder()
.setId(1)
@@ -123,65 +116,95 @@ public class PerfettoProtoLogImplTest {
.setMessage("My Test Debug Log Message %b")
.setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
.setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:123")
).addMessages(
Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
.setMessageId(2)
.setMessage("My Test Verbose Log Message %b")
.setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
.setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:342")
).addMessages(
Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
.setMessageId(3)
.setMessage("My Test Warn Log Message %b")
.setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
.setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:563")
).addMessages(
Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
.setMessageId(4)
.setMessage("My Test Error Log Message %b")
.setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
.setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:156")
).addMessages(
Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
.setMessageId(5)
.setMessage("My Test WTF Log Message %b")
.setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
.setGroupId(1)
+ .setLocation("com/test/MyTestClass.java:192")
);
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
ViewerConfigInputStreamProvider.class);
Mockito.when(viewerConfigInputStreamProvider.getInputStream())
- .thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
+ .thenAnswer(it -> new AutoClosableProtoInputStream(
+ sViewerConfigBuilder.build().toByteArray()));
+
+ sCacheUpdater = () -> {};
+ sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+
+ final ProtoLogDataSourceBuilder dataSourceBuilder =
+ (onStart, onFlush, onStop) -> new ProtoLogDataSource(
+ onStart, onFlush, onStop, TEST_PROTOLOG_DATASOURCE_NAME);
+ final ViewerConfigFileTracer tracer = (dataSource, viewerConfigFilePath) -> {
+ Utils.dumpViewerConfig(dataSource, () -> {
+ if (!viewerConfigFilePath.equals(MOCK_VIEWER_CONFIG_FILE)) {
+ throw new RuntimeException(
+ "Unexpected viewer config file path provided");
+ }
+ return new AutoClosableProtoInputStream(sViewerConfigBuilder.build().toByteArray());
+ });
+ };
+ sProtoLogConfigurationService =
+ new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer);
+
+ sProtoLog = new ProcessedPerfettoProtoLogImpl(
+ MOCK_VIEWER_CONFIG_FILE, viewerConfigInputStreamProvider, sReader,
+ () -> sCacheUpdater.run(), TestProtoLogGroup.values(), dataSourceBuilder,
+ sProtoLogConfigurationService);
+
+ busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME);
+ }
+
+ @Before
+ public void before() {
+ Mockito.reset(sReader);
- mCacheUpdater = () -> {};
- mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
- mProtoLog = new PerfettoProtoLogImpl(
- viewerConfigInputStreamProvider, mReader, new TreeMap<>(),
- () -> mCacheUpdater.run());
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
}
@After
public void tearDown() {
- if (mFile != null) {
- //noinspection ResultOfMethodCallIgnored
- mFile.delete();
- }
ProtoLogImpl.setSingleInstance(null);
}
@Test
public void isEnabled_returnsFalseByDefault() {
- assertFalse(mProtoLog.isProtoEnabled());
+ assertFalse(sProtoLog.isProtoEnabled());
}
@Test
public void isEnabled_returnsTrueAfterStart() {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
try {
traceMonitor.start();
- assertTrue(mProtoLog.isProtoEnabled());
+ assertTrue(sProtoLog.isProtoEnabled());
} finally {
traceMonitor.stop(mWriter);
}
@@ -189,36 +212,38 @@ public class PerfettoProtoLogImplTest {
@Test
public void isEnabled_returnsFalseAfterStop() {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
try {
traceMonitor.start();
- assertTrue(mProtoLog.isProtoEnabled());
+ assertTrue(sProtoLog.isProtoEnabled());
} finally {
traceMonitor.stop(mWriter);
}
- assertFalse(mProtoLog.isProtoEnabled());
+ assertFalse(sProtoLog.isProtoEnabled());
}
@Test
public void defaultMode() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(false).build();
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(false, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
try {
traceMonitor.start();
// Shouldn't be logging anything except WTF unless explicitly requested in the group
// override.
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -232,23 +257,25 @@ public class PerfettoProtoLogImplTest {
@Test
public void respectsOverrideConfigs_defaultMode() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(
+ true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
- TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG, true)))
- .build();
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG, true)),
+ TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
try {
traceMonitor.start();
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -267,22 +294,24 @@ public class PerfettoProtoLogImplTest {
@Test
public void respectsOverrideConfigs_allEnabledMode() throws IOException {
PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(
+ true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
- TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN, false)))
- .build();
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN, false)),
+ TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
try {
traceMonitor.start();
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -298,21 +327,21 @@ public class PerfettoProtoLogImplTest {
@Test
public void respectsAllEnabledMode() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true, List.of())
- .build();
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
try {
traceMonitor.start();
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, null, new Object[]{true});
- mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, null, new Object[]{true});
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
+ LogDataType.BOOLEAN, new Object[]{true});
+ sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
+ LogDataType.BOOLEAN, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -329,83 +358,66 @@ public class PerfettoProtoLogImplTest {
}
@Test
- public void log_logcatEnabledExternalMessage() {
- when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
- PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ public void log_logcatEnabled() {
+ when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO),
eq("test true 10000 % 0x7530 test 3.0E-6"));
- verify(mReader).getViewerString(eq(1234L));
+ verify(sReader).getViewerString(eq(1234L));
}
@Test
public void log_logcatEnabledInvalidMessage() {
- when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
- PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO),
- eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
- verify(mReader).getViewerString(eq(1234L));
+ eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", "
+ + "args=(true, 10000, 1.0E-4, 2.0E-5, test)"));
+ verify(sReader).getViewerString(eq(1234L));
}
@Test
- public void log_logcatEnabledInlineMessage() {
- when(mReader.getViewerString(anyLong())).thenReturn("test %d");
- PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ public void log_logcatEnabledNoMessageThrows() {
+ when(sReader.getViewerString(anyLong())).thenReturn(null);
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
- implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
- new Object[]{5});
-
- verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("test 5"));
- verify(mReader, never()).getViewerString(anyLong());
- }
-
- @Test
- public void log_logcatEnabledNoMessage() {
- when(mReader.getViewerString(anyLong())).thenReturn(null);
- PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
- TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
- TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
-
- implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
- new Object[]{5});
-
- verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
- verify(mReader).getViewerString(eq(1234L));
+ var assertion = assertThrows(RuntimeException.class, () ->
+ implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ new Object[]{5}));
+ Truth.assertThat(assertion).hasMessageThat()
+ .contains("Failed to decode message for logcat");
}
@Test
public void log_logcatDisabled() {
- when(mReader.getViewerString(anyLong())).thenReturn("test %d");
- PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ when(sReader.getViewerString(anyLong())).thenReturn("test %d");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
- verify(mReader, never()).getViewerString(anyLong());
+ verify(sReader, never()).getViewerString(anyLong());
}
@Test
@@ -414,18 +426,20 @@ public class PerfettoProtoLogImplTest {
ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_INFO,
"My test message :: %s, %d, %o, %x, %f, %e, %g, %b");
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
long before;
long after;
try {
+ assertFalse(sProtoLog.isProtoEnabled());
traceMonitor.start();
- assertTrue(mProtoLog.isProtoEnabled());
+ assertTrue(sProtoLog.isProtoEnabled());
before = SystemClock.elapsedRealtimeNanos();
- mProtoLog.log(
+ sProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b1110101001010100, null,
+ 0b1110101001010100,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -441,12 +455,67 @@ public class PerfettoProtoLogImplTest {
Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
.isAtMost(after);
Truth.assertThat(protolog.messages.getFirst().getMessage())
- .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true");
+ .isEqualTo(
+ "My test message :: test, 1, 2, 3, 0.400000, 5.000000e-01, 0.6, true");
+ }
+
+ @Test
+ public void log_noProcessing() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ long before;
+ long after;
+ try {
+ traceMonitor.start();
+ assertTrue(sProtoLog.isProtoEnabled());
+
+ before = SystemClock.elapsedRealtimeNanos();
+ sProtoLog.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
+ "My test message :: %s, %d, %x, %f, %b",
+ "test", 1, 3, 0.4, true);
+ after = SystemClock.elapsedRealtimeNanos();
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtLeast(before);
+ Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
+ .isAtMost(after);
+ Truth.assertThat(protolog.messages.getFirst().getMessage())
+ .isEqualTo("My test message :: test, 1, 3, 0.400000, true");
+ }
+
+ @Test
+ public void supportsLocationInformation() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getLocation())
+ .isEqualTo("com/test/MyTestClass.java:123");
}
private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
final long messageId = new Random().nextLong();
- mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ sViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
.setMessageId(messageId)
.setMessage(message)
.setLevel(logLevel)
@@ -461,18 +530,15 @@ public class PerfettoProtoLogImplTest {
final long messageHash = addMessageToConfig(
ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_INFO,
"My test message :: %s, %d, %f, %b");
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
- long before;
- long after;
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
try {
traceMonitor.start();
- before = SystemClock.elapsedRealtimeNanos();
- mProtoLog.log(
+ sProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b01100100, null,
+ 0b01100100,
new Object[]{"test", 1, 0.1, true});
- after = SystemClock.elapsedRealtimeNanos();
} finally {
traceMonitor.stop(mWriter);
}
@@ -483,12 +549,13 @@ public class PerfettoProtoLogImplTest {
@Test
public void log_protoDisabled() throws Exception {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(false).build();
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(false, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
try {
traceMonitor.start();
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, null, new Object[]{true});
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ 0b11, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -501,18 +568,20 @@ public class PerfettoProtoLogImplTest {
@Test
public void stackTraceTrimmed() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(
+ true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
- true)))
- .build();
+ true)),
+ TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
try {
traceMonitor.start();
- ProtoLogImpl.setSingleInstance(mProtoLog);
+ ProtoLogImpl.setSingleInstance(sProtoLog);
ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, null, true);
+ 0b11, true);
} finally {
traceMonitor.stop(mWriter);
}
@@ -527,27 +596,28 @@ public class PerfettoProtoLogImplTest {
Truth.assertThat(stacktrace).doesNotContain(DataSource.class.getSimpleName() + ".java");
Truth.assertThat(stacktrace)
.doesNotContain(ProtoLogImpl.class.getSimpleName() + ".java");
- Truth.assertThat(stacktrace).contains(PerfettoProtoLogImplTest.class.getSimpleName());
+ Truth.assertThat(stacktrace)
+ .contains(ProcessedPerfettoProtoLogImplTest.class.getSimpleName());
Truth.assertThat(stacktrace).contains("stackTraceTrimmed");
}
@Test
public void cacheIsUpdatedWhenTracesStartAndStop() {
final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0);
- mCacheUpdater = cacheUpdateCallCount::incrementAndGet;
+ sCacheUpdater = cacheUpdateCallCount::incrementAndGet;
- PerfettoTraceMonitor traceMonitor1 =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
- List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
- TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
- false)))
- .build();
+ PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)), TEST_PROTOLOG_DATASOURCE_NAME
+ ).build();
PerfettoTraceMonitor traceMonitor2 =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
- false)))
+ false)), TEST_PROTOLOG_DATASOURCE_NAME)
.build();
Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(0);
@@ -576,95 +646,216 @@ public class PerfettoProtoLogImplTest {
@Test
public void isEnabledUpdatesBasedOnRunningTraces() {
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isTrue();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
PerfettoTraceMonitor traceMonitor1 =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
- false)))
+ false)), TEST_PROTOLOG_DATASOURCE_NAME)
.build();
PerfettoTraceMonitor traceMonitor2 =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
- false)))
+ false)), TEST_PROTOLOG_DATASOURCE_NAME)
.build();
try {
traceMonitor1.start();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
.isTrue();
try {
traceMonitor2.start();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
LogLevel.VERBOSE)).isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
.isTrue();
} finally {
traceMonitor2.stop(mWriter);
}
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isTrue();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
.isTrue();
} finally {
traceMonitor1.stop(mWriter);
}
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
- .isTrue();
+ }
+
+ @Test
+ public void supportsNullString() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ try {
+ traceMonitor.start();
+
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "My test null string: %s", (Object) null);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("My test null string: null");
+ }
+
+ @Test
+ public void supportNullParams() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ try {
+ traceMonitor.start();
+
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "My null args: %d, %f, %b", null, null, null);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(1);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("My null args: 0, 0.000000, false");
+ }
+
+ @Test
+ public void handlesConcurrentTracingSessions() throws IOException {
+ PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(true, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+
+ final ResultWriter writer2 = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ try {
+ traceMonitor1.start();
+ traceMonitor2.start();
+
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
+ LogDataType.BOOLEAN, new Object[]{true});
+ } finally {
+ traceMonitor1.stop(mWriter);
+ traceMonitor2.stop(writer2);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protologFromMonitor1 = reader.readProtoLogTrace();
+
+ final ResultReader reader2 = new ResultReader(writer2.write(), mTraceConfig);
+ final ProtoLogTrace protologFromMonitor2 = reader2.readProtoLogTrace();
+
+ Truth.assertThat(protologFromMonitor1.messages).hasSize(1);
+ Truth.assertThat(protologFromMonitor1.messages.get(0).getMessage())
+ .isEqualTo("My Test Debug Log Message true");
+
+ Truth.assertThat(protologFromMonitor2.messages).hasSize(1);
+ Truth.assertThat(protologFromMonitor2.messages.get(0).getMessage())
+ .isEqualTo("My Test Debug Log Message true");
+ }
+
+ @Test
+ public void usesDefaultLogFromLevel() throws IOException {
+ PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
+ .enableProtoLog(LogLevel.WARN, List.of(), TEST_PROTOLOG_DATASOURCE_NAME)
+ .build();
+ try {
+ traceMonitor.start();
+ sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
+ "This message should not be logged");
+ sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP,
+ "This message should be logged %d", 123);
+ sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP,
+ "This message should also be logged %d", 567);
+ } finally {
+ traceMonitor.stop(mWriter);
+ }
+
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final ProtoLogTrace protolog = reader.readProtoLogTrace();
+
+ Truth.assertThat(protolog.messages).hasSize(2);
+
+ Truth.assertThat(protolog.messages.get(0).getLevel())
+ .isEqualTo(LogLevel.WARN);
+ Truth.assertThat(protolog.messages.get(0).getMessage())
+ .isEqualTo("This message should be logged 123");
+
+ Truth.assertThat(protolog.messages.get(1).getLevel())
+ .isEqualTo(LogLevel.ERROR);
+ Truth.assertThat(protolog.messages.get(1).getMessage())
+ .isEqualTo("This message should also be logged 567");
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
new file mode 100644
index 000000000000..be0c7daebb57
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.endsWith;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogCommandHandlerTest {
+
+ @Mock
+ ProtoLogConfigurationService mProtoLogConfigurationService;
+ @Mock
+ PrintWriter mPrintWriter;
+ @Mock
+ Binder mMockBinder;
+
+ @Test
+ public void printsHelpForAllAvailableCommands() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.onHelp();
+ validateOnHelpPrinted();
+ }
+
+ @Test
+ public void printsHelpIfCommandIsNull() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.onCommand(null);
+ validateOnHelpPrinted();
+ }
+
+ @Test
+ public void handlesGroupListCommand() {
+ Mockito.when(mProtoLogConfigurationService.getGroups())
+ .thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "list" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_TEST_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_OTHER_GROUP"));
+ }
+
+ @Test
+ public void handlesIncompleteGroupsCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommand() {
+ Mockito.when(mProtoLogConfigurationService.getGroups())
+ .thenReturn(new String[] {"MY_GROUP"});
+ Mockito.when(mProtoLogConfigurationService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("LOG_TO_LOGCAT = true"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommandOfUnregisteredGroups() {
+ Mockito.when(mProtoLogConfigurationService.getGroups()).thenReturn(new String[] {});
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("UNREGISTERED"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "groups", "status" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesIncompleteLogcatCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesLogcatEnableCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP");
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
+ new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService)
+ .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ }
+
+ @Test
+ public void handlesLogcatDisableCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP");
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err,
+ new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
+ Mockito.verify(mProtoLogConfigurationService)
+ .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ }
+
+ @Test
+ public void handlesLogcatEnableCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "enable" });
+ Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesLogcatDisableCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter);
+
+ cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, new String[] { "logcat", "disable" });
+ Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+ }
+
+ private void validateOnHelpPrinted() {
+ Mockito.verify(mPrintWriter, times(1)).println(endsWith("help"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(endsWith("groups (list | status)"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(endsWith("logcat (enable | disable) <group>"));
+ Mockito.verify(mPrintWriter, atLeast(0)).println(anyString());
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
new file mode 100644
index 000000000000..a3d03a8278ed
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog;
+
+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 java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.tools.ScenarioBuilder;
+import android.tools.Tag;
+import android.tools.io.ResultArtifactDescriptor;
+import android.tools.io.TraceType;
+import android.tools.traces.TraceConfig;
+import android.tools.traces.TraceConfigs;
+import android.tools.traces.io.ResultReader;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import perfetto.protos.Protolog.ProtoLogViewerConfig;
+import perfetto.protos.ProtologCommon;
+import perfetto.protos.TraceOuterClass.Trace;
+import perfetto.protos.TracePacketOuterClass.TracePacket;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogConfigurationServiceTest {
+
+ private static final String TEST_GROUP = "MY_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
+
+ private static final ProtoLogViewerConfig VIEWER_CONFIG =
+ ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP)
+ .setTag(TEST_GROUP)
+ ).addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Debug Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Verbose Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).build();
+
+ @Mock
+ IProtoLogClient mMockClient;
+
+ @Mock
+ IProtoLogClient mSecondMockClient;
+
+ @Mock
+ IBinder mMockClientBinder;
+
+ @Mock
+ IBinder mSecondMockClientBinder;
+
+ private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ @Captor
+ ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientArgumentCaptor;
+
+ @Captor
+ ArgumentCaptor<IBinder.DeathRecipient> mSecondDeathRecipientArgumentCaptor;
+
+ private File mViewerConfigFile;
+
+ public ProtoLogConfigurationServiceTest() throws IOException {
+ }
+
+ @Before
+ public void setUp() {
+ Mockito.when(mMockClient.asBinder()).thenReturn(mMockClientBinder);
+ Mockito.when(mSecondMockClient.asBinder()).thenReturn(mSecondMockClientBinder);
+
+ try {
+ mViewerConfigFile = File.createTempFile("viewer-config", ".pb");
+ try (var fos = new FileOutputStream(mViewerConfigFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+
+ bos.write(VIEWER_CONFIG.toByteArray());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void canRegisterClientWithGroupsOnly() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
+
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+ Truth.assertThat(service.getGroups()).asList().containsExactly(TEST_GROUP);
+ }
+
+ @Test
+ public void willDumpViewerConfigOnlyOnceOnTraceStop()
+ throws RemoteException, InvalidProtocolBufferException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
+
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ service.registerClient(mMockClient, args);
+ service.registerClient(mSecondMockClient, args);
+
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+
+ traceMonitor.start();
+ traceMonitor.stop(mWriter);
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] traceData = reader.getArtifact()
+ .readBytes(new ResultArtifactDescriptor(TraceType.PERFETTO, Tag.ALL));
+
+ final Trace trace = Trace.parseFrom(traceData);
+
+ final List<TracePacket> configPackets = trace.getPacketList().stream()
+ .filter(it -> it.hasProtologViewerConfig())
+ // Exclude viewer configs from regular system tracing
+ .filter(it ->
+ it.getProtologViewerConfig().getGroups(0).getName().equals(TEST_GROUP))
+ .toList();
+ Truth.assertThat(configPackets).hasSize(1);
+ Truth.assertThat(configPackets.get(0).getProtologViewerConfig().toString())
+ .isEqualTo(VIEWER_CONFIG.toString());
+ }
+
+ @Test
+ public void willDumpViewerConfigOnLastClientDisconnected()
+ throws RemoteException, FileNotFoundException {
+ final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer =
+ Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class);
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer);
+
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ service.registerClient(mMockClient, args);
+ service.registerClient(mSecondMockClient, args);
+
+ Mockito.verify(mMockClientBinder)
+ .linkToDeath(mDeathRecipientArgumentCaptor.capture(), anyInt());
+ Mockito.verify(mSecondMockClientBinder)
+ .linkToDeath(mSecondDeathRecipientArgumentCaptor.capture(), anyInt());
+
+ mDeathRecipientArgumentCaptor.getValue().binderDied();
+ Mockito.verify(tracer, never()).trace(any(), any());
+ mSecondDeathRecipientArgumentCaptor.getValue().binderDied();
+ Mockito.verify(tracer).trace(any(), eq(mViewerConfigFile.getAbsolutePath()));
+ }
+
+ @Test
+ public void sendEnableLoggingToLogcatToClient() throws RemoteException {
+ final var service = new ProtoLogConfigurationServiceImpl();
+
+ final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+ service.enableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(true),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+
+ @Test
+ public void sendDisableLoggingToLogcatToClient() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
+
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, true));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+ service.disableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(false),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+
+ @Test
+ public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
+
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+ service.enableProtoLogToLogcat(OTHER_TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+ Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any());
+ }
+
+ @Test
+ public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
+ final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl();
+
+ Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
+ service.enableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+ final ProtoLogConfigurationServiceImpl.RegisterClientArgs args =
+ new ProtoLogConfigurationServiceImpl.RegisterClientArgs()
+ .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs
+ .GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(true),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
index 60456f9ea10f..0496240f01e4 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -58,51 +58,50 @@ public class ProtoLogImplTest {
public void d_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.DEBUG), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void v_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.VERBOSE), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void i_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.INFO), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void w_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234,
- 4321, "test %d");
+ ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.WARN), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
public void e_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
@Test
@@ -110,10 +109,10 @@ public class ProtoLogImplTest {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
- 1234, 4321, "test %d");
+ 1234, 4321);
verify(mockedProtoLog).log(eq(LogLevel.WTF), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq(new Object[]{}));
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
new file mode 100644
index 000000000000..3d1e208189b0
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 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.internal.protolog;
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link ProtoLog}. */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogTest {
+
+ @Test
+ public void canRunProtoLogInitMultipleTimes() {
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_1);
+ ProtoLog.init(TEST_GROUP_2);
+ ProtoLog.init(TEST_GROUP_1, TEST_GROUP_2);
+
+ final var instance = ProtoLog.getSingleInstance();
+ Truth.assertThat(instance.getRegisteredGroups())
+ .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+ }
+
+ @Test
+ public void deduplicatesRegisteringDuplicateGroup() {
+ ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2);
+
+ final var instance = ProtoLog.getSingleInstance();
+ Truth.assertThat(instance.getRegisteredGroups())
+ .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+ }
+
+ @Test
+ public void throwOnRegisteringGroupsWithIdCollisions() {
+ final var assertion = assertThrows(RuntimeException.class,
+ () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_WITH_COLLISION, TEST_GROUP_2));
+
+ Truth.assertThat(assertion).hasMessageThat()
+ .contains("" + TEST_GROUP_WITH_COLLISION.getId());
+ Truth.assertThat(assertion).hasMessageThat().contains("collision");
+ }
+
+ private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
+ private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+ private static final IProtoLogGroup TEST_GROUP_WITH_COLLISION =
+ new ProtoLogGroup("TEST_TAG_WITH_COLLISION", 1);
+
+ private static class ProtoLogGroup implements IProtoLogGroup {
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+ private final int mId;
+
+ ProtoLogGroup(String tag, int id) {
+ this(true, true, false, tag, id);
+ }
+
+ ProtoLogGroup(
+ boolean enabled, boolean logToProto, boolean logToLogcat, String tag, int id) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ this.mId = id;
+ }
+
+ @Override
+ public String name() {
+ return mTag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
new file mode 100644
index 000000000000..9e029a8d5e57
--- /dev/null
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 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.internal.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.os.Build;
+import android.platform.test.annotations.Presubmit;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import perfetto.protos.ProtologCommon;
+
+import java.io.File;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogViewerConfigReaderTest {
+ private static final String TEST_GROUP_NAME = "MY_TEST_GROUP";
+ private static final String TEST_GROUP_TAG = "TEST";
+
+ private static final String OTHER_TEST_GROUP_NAME = "MY_OTHER_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP_TAG = "OTHER_TEST";
+
+ private static final byte[] TEST_VIEWER_CONFIG =
+ perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP_NAME)
+ .setTag(TEST_GROUP_TAG)
+ ).addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(2)
+ .setName(OTHER_TEST_GROUP_NAME)
+ .setTag(OTHER_TEST_GROUP_TAG)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Log Message 1 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Log Message 2 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(3)
+ .setMessage("My Test Log Message 3 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(4)
+ .setMessage("My Test Log Message 4 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
+ .setGroupId(2)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(5)
+ .setMessage("My Test Log Message 5 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
+ .setGroupId(2)
+ ).build().toByteArray();
+
+ private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
+ () -> new AutoClosableProtoInputStream(TEST_VIEWER_CONFIG);
+
+ private ProtoLogViewerConfigReader mConfig;
+
+ @Before
+ public void before() {
+ mConfig = new ProtoLogViewerConfigReader(mViewerConfigInputStreamProvider);
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertEquals("My Test Log Message 1 %b", mConfig.getViewerString(1));
+ assertEquals("My Test Log Message 2 %b", mConfig.getViewerString(2));
+ assertEquals("My Test Log Message 3 %b", mConfig.getViewerString(3));
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
+ }
+
+ @Test
+ public void unloadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME, OTHER_TEST_GROUP_NAME });
+ mConfig.unloadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(1));
+ assertNull(mConfig.getViewerString(2));
+ assertNull(mConfig.getViewerString(3));
+ assertEquals("My Test Log Message 4 %b", mConfig.getViewerString(4));
+ assertEquals("My Test Log Message 5 %b", mConfig.getViewerString(5));
+
+ mConfig.unloadViewerConfig(new String[] { OTHER_TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
+ }
+
+ @Test
+ public void viewerConfigIsOnDevice() {
+ Assume.assumeFalse(Build.FINGERPRINT.contains("robolectric"));
+
+ final String[] viewerConfigPaths;
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ viewerConfigPaths = new String[] {
+ "/system_ext/etc/wmshell.protolog.pb",
+ "/system/etc/core.protolog.pb",
+ };
+ } else {
+ viewerConfigPaths = new String[] {
+ "/system_ext/etc/wmshell.protolog.json.gz",
+ "/system/etc/protolog.conf.json.gz",
+ };
+ }
+
+ for (final var viewerConfigPath : viewerConfigPaths) {
+ File f = new File(viewerConfigPath);
+
+ Truth.assertWithMessage(f.getAbsolutePath() + " exists").that(f.exists()).isTrue();
+ }
+
+ }
+
+ @Test
+ public void loadUnloadAndReloadViewerConfig() {
+ loadViewerConfig();
+ unloadViewerConfig();
+ loadViewerConfig();
+ unloadViewerConfig();
+ }
+}
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
index be9fb1b309f6..ce519b7a1576 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -67,7 +67,8 @@ public class ProtologDataSourceTest {
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds = new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {});
+ final ProtoLogDataSource ds =
+ new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
@@ -154,7 +155,7 @@ public class ProtologDataSourceTest {
private ProtoLogDataSource.TlsState createTlsState(
DataSourceConfigOuterClass.DataSourceConfig config) {
final ProtoLogDataSource ds =
- Mockito.spy(new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {}));
+ Mockito.spy(new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}));
ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
final ProtoLogDataSource.Instance dsInstance = Mockito.spy(
diff --git a/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java b/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
index 09236ffebdf4..459db8a0a1ac 100644
--- a/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/BitmapUploadActivity.java
@@ -74,6 +74,9 @@ public class BitmapUploadActivity extends AppCompatActivity {
}
}
+ private ObjectAnimator mColorValueAnimator;
+ private ObjectAnimator mYAnimator;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -81,16 +84,28 @@ public class BitmapUploadActivity extends AppCompatActivity {
// animate color to force bitmap uploads
UploadView uploadView = findViewById(R.id.upload_view);
- ObjectAnimator colorValueAnimator = ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
- colorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
- colorValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
- colorValueAnimator.start();
+ mColorValueAnimator = ObjectAnimator.ofInt(uploadView, "colorValue", 0, 255);
+ mColorValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mColorValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ mColorValueAnimator.start();
// animate scene root to guarantee there's a minimum amount of GPU rendering work
View uploadRoot = findViewById(R.id.upload_root);
- ObjectAnimator yAnimator = ObjectAnimator.ofFloat(uploadRoot, "translationY", 0, 100);
- yAnimator.setRepeatMode(ValueAnimator.REVERSE);
- yAnimator.setRepeatCount(ValueAnimator.INFINITE);
- yAnimator.start();
+ mYAnimator = ObjectAnimator.ofFloat(uploadRoot, "translationY", 0, 100);
+ mYAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mYAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ mYAnimator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mColorValueAnimator != null) {
+ mColorValueAnimator.cancel();
+ }
+
+ if (mYAnimator != null) {
+ mYAnimator.cancel();
+ }
}
}
diff --git a/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java b/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java
index 882163bd6b0e..9d10f76198c3 100644
--- a/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/FullscreenOverdrawActivity.java
@@ -66,18 +66,29 @@ public class FullscreenOverdrawActivity extends AppCompatActivity {
return PixelFormat.OPAQUE;
}
}
+
+ private ObjectAnimator mObjectAnimator;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OverdrawDrawable overdraw = new OverdrawDrawable();
getWindow().setBackgroundDrawable(overdraw);
-
setContentView(new View(this));
- ObjectAnimator objectAnimator = ObjectAnimator.ofInt(overdraw, "colorValue", 0, 255);
- objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
- objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
- objectAnimator.start();
+ mObjectAnimator = ObjectAnimator.ofInt(overdraw, "colorValue", 0, 255);
+ mObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
+
+ mObjectAnimator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mObjectAnimator != null) {
+ mObjectAnimator.cancel();
+ }
}
}
diff --git a/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java b/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
index b26a660981da..1b28dc29d6aa 100644
--- a/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/GlTextureViewActivity.java
@@ -33,6 +33,7 @@ import com.android.test.uibench.opengl.ImageFlipRenderThread;
public class GlTextureViewActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
private ImageFlipRenderThread mRenderThread;
private TextureView mTextureView;
+ private ObjectAnimator mAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -54,17 +55,17 @@ public class GlTextureViewActivity extends AppCompatActivity implements TextureV
int distance = Math.max(mTextureView.getWidth(), mTextureView.getHeight());
mTextureView.setCameraDistance(distance * metrics.density);
- ObjectAnimator animator = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
- animator.setRepeatMode(ObjectAnimator.REVERSE);
- animator.setRepeatCount(ObjectAnimator.INFINITE);
- animator.setDuration(4000);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ mAnimator = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
+ mAnimator.setRepeatMode(ObjectAnimator.REVERSE);
+ mAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+ mAnimator.setDuration(4000);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTextureView.invalidate();
}
});
- animator.start();
+ mAnimator.start();
}
@Override
@@ -86,4 +87,11 @@ public class GlTextureViewActivity extends AppCompatActivity implements TextureV
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
} \ No newline at end of file
diff --git a/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java b/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
index 76ed1ae4e445..f1e96c80c85a 100644
--- a/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/InvalidateActivity.java
@@ -51,6 +51,7 @@ public class InvalidateActivity extends AppCompatActivity {
}
private ColorView[][] mColorViews;
+ private ObjectAnimator mAnimator;
@SuppressWarnings("unused")
public void setColorValue(int colorValue) {
@@ -80,9 +81,17 @@ public class InvalidateActivity extends AppCompatActivity {
}
}
- ObjectAnimator animator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
- animator.setRepeatMode(ValueAnimator.REVERSE);
- animator.setRepeatCount(ValueAnimator.INFINITE);
- animator.start();
+ mAnimator = ObjectAnimator.ofInt(this, "colorValue", 0, 255);
+ mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ mAnimator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
}
}
diff --git a/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java b/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java
index 804ced14d522..95635720d4f9 100644
--- a/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/InvalidateTreeActivity.java
@@ -33,6 +33,7 @@ public class InvalidateTreeActivity extends AppCompatActivity {
private final ArrayList<LinearLayout> mLayouts = new ArrayList<>();
private int mColorToggle = 0;
+ private ObjectAnimator mAnimator;
private void createQuadTree(LinearLayout parent, int remainingDepth) {
mLayouts.add(parent);
@@ -71,9 +72,17 @@ public class InvalidateTreeActivity extends AppCompatActivity {
createQuadTree(root, 8);
setContentView(root);
- ObjectAnimator animator = ObjectAnimator.ofInt(this, "ignoredValue", 0, 1000);
- animator.setRepeatMode(ValueAnimator.REVERSE);
- animator.setRepeatCount(ValueAnimator.INFINITE);
- animator.start();
+ mAnimator = ObjectAnimator.ofInt(this, "ignoredValue", 0, 1000);
+ mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ mAnimator.start();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
}
}
diff --git a/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java b/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java
index 80d495df142c..cb26edcf336c 100644
--- a/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/ResizeHWLayerActivity.java
@@ -30,6 +30,8 @@ import android.widget.FrameLayout;
*/
public class ResizeHWLayerActivity extends AppCompatActivity {
+ private ValueAnimator mAnimator;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -43,10 +45,10 @@ public class ResizeHWLayerActivity extends AppCompatActivity {
PropertyValuesHolder pvhWidth = PropertyValuesHolder.ofInt("width", width, 1);
PropertyValuesHolder pvhHeight = PropertyValuesHolder.ofInt("height", height, 1);
final LayoutParams params = child.getLayoutParams();
- ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(pvhWidth, pvhHeight);
- animator.setRepeatMode(ValueAnimator.REVERSE);
- animator.setRepeatCount(ValueAnimator.INFINITE);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ mAnimator = ValueAnimator.ofPropertyValuesHolder(pvhWidth, pvhHeight);
+ mAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
params.width = (Integer)valueAnimator.getAnimatedValue("width");
@@ -54,7 +56,15 @@ public class ResizeHWLayerActivity extends AppCompatActivity {
child.requestLayout();
}
});
- animator.start();
+ mAnimator.start();
setContentView(child);
}
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
}
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 56845aeb6a2c..51d57f0a0de9 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -18,6 +18,10 @@ package com.android.server.usb;
import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -31,12 +35,15 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.flags.Flags;
+import android.hardware.usb.UsbPort;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.LocalServices;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -71,26 +78,38 @@ public class UsbServiceTest {
private static final int TEST_SECOND_CALLER_ID = 2000;
+ private static final int TEST_INTERNAL_REQUESTER_REASON_1 = 100;
+
+ private static final int TEST_INTERNAL_REQUESTER_REASON_2 = 200;
+
private UsbService mUsbService;
+ private UsbManagerInternal mUsbManagerInternal;
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL);
+ LocalServices.removeAllServicesForTest();
MockitoAnnotations.initMocks(this);
- when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
- eq(mCallback), any())).thenReturn(true);
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(),
+ eq(TEST_TRANSACTION_ID), eq(mCallback), any())).thenReturn(true);
mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
mUserManager, mUsbSettingsManager);
+ mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
+ assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
+ .that(mUsbManagerInternal).isNotNull();
}
- private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+ private void assertToggleUsbSuccessfully(int requester, boolean enable,
+ boolean isInternalRequest) {
assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
- TEST_TRANSACTION_ID, mCallback, uid));
+ TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
enable, TEST_TRANSACTION_ID, mCallback, null);
@@ -100,9 +119,10 @@ public class UsbServiceTest {
clearInvocations(mCallback);
}
- private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+ private void assertToggleUsbFailed(int requester, boolean enable,
+ boolean isInternalRequest) throws Exception {
assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
- TEST_TRANSACTION_ID, mCallback, uid));
+ TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
verifyZeroInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
@@ -116,15 +136,16 @@ public class UsbServiceTest {
*/
@Test
public void disableUsb_successfullyDisable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
}
/**
- * Verify enableUsbData successfully enables USB port without error given no other stakers
+ * Verify enableUsbData successfully enables USB port without error given
+ * no other stakers
*/
@Test
public void enableUsbWhenNoOtherStakers_successfullyEnable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
}
/**
@@ -132,47 +153,132 @@ public class UsbServiceTest {
*/
@Test
public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
- assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+ assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true, false);
}
/**
- * Verify enableUsbData successfully enables USB port when the last staker is removed
+ * Verify enableUsbData successfully enables USB port when the last staker
+ * is removed
*/
@Test
public void enableUsbByTheOnlyStaker_successfullyEnable() {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
}
/**
- * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+ * Verify enableUsbDataWhileDockedInternal does not enable USB port if other
+ * stakers are present
*/
@Test
public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
throws RemoteException {
- assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mCallback, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID, false);
verifyZeroInteractions(mUsbPortManager);
verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
}
/**
- * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
- * not present
+ * Verify enableUsbDataWhileDockedInternal does enable USB port if other
+ * stakers are not present
*/
@Test
public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
- mCallback, TEST_SECOND_CALLER_ID);
+ mCallback, TEST_SECOND_CALLER_ID, false);
verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
mCallback, null);
verifyZeroInteractions(mCallback);
}
+
+ /**
+ * Verify enableUsbData successfully enables USB port without error given no
+ * other stakers for internal requests
+ */
+ @Test
+ public void enableUsbWhenNoOtherStakers_forInternalRequest_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other internal stakers
+ * are present for internal requests
+ */
+ @Test
+ public void enableUsbPortWithOtherInternalStakers_forInternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+ assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other external stakers
+ * are present for internal requests
+ */
+ @Test
+ public void enableUsbPortWithOtherExternalStakers_forInternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
+
+ assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other internal stakers
+ * are present for external requests
+ */
+ @Test
+ public void enableUsbPortWithOtherInternalStakers_forExternalRequest_failsToEnable()
+ throws Exception {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+ assertToggleUsbFailed(TEST_FIRST_CALLER_ID, true, false);
+ }
+
+ /**
+ * Verify enableUsbData successfully enables USB port when the last staker
+ * is removed for internal requests
+ */
+ @Test
+ public void enableUsbByTheOnlyStaker_forInternalRequest_successfullyEnable() {
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, false);
+
+ assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
+ }
+
+ /**
+ * Verify USB Manager internal calls mPortManager to get UsbPorts
+ */
+ @Test
+ public void usbManagerInternal_getPorts_callsPortManager() {
+ when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
+
+ UsbPort[] ports = mUsbManagerInternal.getPorts();
+
+ verify(mUsbPortManager).getPorts();
+ assertEquals(ports.length, 0);
+ }
+
+ @Test
+ public void usbManagerInternal_enableUsbData_successfullyEnable() {
+ boolean desiredEnableState = true;
+
+ assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
+ TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+
+ verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+ desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
+ verifyZeroInteractions(mCallback);
+ clearInvocations(mUsbPortManager);
+ clearInvocations(mCallback);
+ }
}
diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS
new file mode 100644
index 000000000000..d2e1f815e8dc
--- /dev/null
+++ b/tests/broadcasts/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316181
+include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
new file mode 100644
index 000000000000..47166a713580
--- /dev/null
+++ b/tests/broadcasts/unit/Android.bp
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2024 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_framework_backstage_power",
+}
+
+android_test {
+ name: "BroadcastUnitTests",
+ srcs: ["src/**/*.java"],
+ defaults: [
+ "modules-utils-extended-mockito-rule-defaults",
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "truth",
+ "flag-junit",
+ "android.app.flags-aconfig-java",
+ ],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
new file mode 100644
index 000000000000..e9c5248e4d98
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.broadcasts.unit" >
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.broadcasts.unit"
+ android:label="Broadcasts Unit Tests"/>
+</manifest> \ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
new file mode 100644
index 000000000000..b91e4783b69e
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidTest.xml
@@ -0,0 +1,29 @@
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Runs Broadcasts tests">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="BroadcastUnitTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="BroadcastUnitTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.broadcasts.unit" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration> \ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
new file mode 100644
index 000000000000..8919fdcd7a3f
--- /dev/null
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "BroadcastUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
new file mode 100644
index 000000000000..b7c412dea999
--- /dev/null
+++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 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 android.app;
+
+import static android.content.Intent.ACTION_BATTERY_CHANGED;
+import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArrayMap;
+
+import androidx.annotation.GuardedBy;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BroadcastStickyCacheTest {
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(SystemProperties.class)
+ .build();
+
+ private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
+ ACTION_BATTERY_CHANGED);
+
+ private final TestSystemProps mTestSystemProps = new TestSystemProps();
+
+ @Before
+ public void setUp() {
+ doAnswer(invocation -> {
+ final String name = invocation.getArgument(0);
+ final long value = Long.parseLong(invocation.getArgument(1));
+ mTestSystemProps.add(name, value);
+ return null;
+ }).when(() -> SystemProperties.set(anyString(), anyString()));
+ doAnswer(invocation -> {
+ final String name = invocation.getArgument(0);
+ final TestSystemProps.Handle testHandle = mTestSystemProps.query(name);
+ if (testHandle == null) {
+ return null;
+ }
+ final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class);
+ doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong());
+ return handle;
+ }).when(() -> SystemProperties.find(anyString()));
+ }
+
+ @After
+ public void tearDown() {
+ mTestSystemProps.clear();
+ BroadcastStickyCache.clearForTest();
+ }
+
+ @Test
+ public void testUseCache_nullFilter() {
+ assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache_noActions() {
+ final IntentFilter filter = new IntentFilter();
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache_multipleActions() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DEVICE_STORAGE_LOW);
+ filter.addAction(ACTION_BATTERY_CHANGED);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache_valueNotSet() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.add(filter, intent);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
+ }
+
+ @Test
+ public void testUseCache_versionMismatch() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.add(filter, intent);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testAdd() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.add(filter, intent);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
+ Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
+ assertThat(actualIntent).isNotNull();
+ assertEquals(actualIntent, intent);
+
+ intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 99);
+ BroadcastStickyCache.add(filter, intent);
+ actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
+ assertThat(actualIntent).isNotNull();
+ assertEquals(actualIntent, intent);
+ }
+
+ @Test
+ public void testIncrementVersion_propExists() {
+ SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
+
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102);
+ }
+
+ @Test
+ public void testIncrementVersion_propNotExists() {
+ // Verify that the property doesn't exist
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
+ }
+
+ @Test
+ public void testIncrementVersionIfExists_propExists() {
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3);
+ }
+
+ @Test
+ public void testIncrementVersionIfExists_propNotExists() {
+ // Verify that the property doesn't exist
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+ // Verify that property is not added as part of the querying.
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+ }
+
+ private void assertEquals(Intent actualIntent, Intent expectedIntent) {
+ assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
+ assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
+ }
+
+ private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
+ assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
+ .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
+ }
+
+ private static final class TestSystemProps {
+ @GuardedBy("mSysProps")
+ private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
+
+ public void add(String name, long value) {
+ synchronized (mSysProps) {
+ mSysProps.put(name, value);
+ }
+ }
+
+ public long get(String name, long defaultValue) {
+ synchronized (mSysProps) {
+ final int idx = mSysProps.indexOfKey(name);
+ return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
+ }
+ }
+
+ public Handle query(String name) {
+ synchronized (mSysProps) {
+ return mSysProps.containsKey(name) ? new Handle(name) : null;
+ }
+ }
+
+ public void clear() {
+ synchronized (mSysProps) {
+ mSysProps.clear();
+ }
+ }
+
+ public class Handle {
+ private final String mName;
+
+ Handle(String name) {
+ mName = name;
+ }
+
+ public long getLong(long defaultValue) {
+ return get(mName, defaultValue);
+ }
+ }
+ }
+}
diff --git a/tests/graphics/HwAccelerationTest/AndroidManifest.xml b/tests/graphics/HwAccelerationTest/AndroidManifest.xml
index db3a992b9c7b..05b2f4c53b15 100644
--- a/tests/graphics/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/graphics/HwAccelerationTest/AndroidManifest.xml
@@ -24,7 +24,7 @@
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
- <uses-sdk android:minSdkVersion="21"/>
+ <uses-sdk android:minSdkVersion="21" />
<application android:label="HwUi"
android:theme="@android:style/Theme.Material.Light">
@@ -409,6 +409,24 @@
</intent-filter>
</activity>
+ <activity android:name="ScrollingZAboveSurfaceView"
+ android:label="SurfaceView/Z-Above scrolling"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="ScrollingZAboveScaledSurfaceView"
+ android:label="SurfaceView/Z-Above scrolling, scaled surface"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="com.android.test.hwui.TEST"/>
+ </intent-filter>
+ </activity>
+
<activity android:name="StretchySurfaceViewActivity"
android:label="SurfaceView/Stretchy Movement"
android:exported="true">
diff --git a/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml
new file mode 100644
index 000000000000..31e5774dd1ad
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context=".MainActivity">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Above the ScrollView"
+ android:textColor="#FFFFFFFF"
+ android:background="#FF444444"
+ android:padding="32dp" />
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Header"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <SurfaceView
+ android:layout_width="match_parent"
+ android:layout_height="500dp"
+ android:id="@+id/surfaceview" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Scrolling Item"
+ android:background="#FFCCCCCC"
+ android:padding="32dp" />
+
+ </LinearLayout>
+
+ </ScrollView>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Below the ScrollView"
+ android:textColor="#FFFFFFFF"
+ android:background="#FF444444"
+ android:padding="32dp" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt
new file mode 100644
index 000000000000..59ae885664db
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 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.test.hwui
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+class ScrollingZAboveScaledSurfaceView : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.scrolling_zabove_surfaceview)
+
+ findViewById<SurfaceView>(R.id.surfaceview).apply {
+ setZOrderOnTop(true)
+ holder.setFixedSize(1000, 2000)
+ holder.addCallback(object : SurfaceHolder.Callback {
+ override fun surfaceCreated(p0: SurfaceHolder) {
+
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ holder.unlockCanvasAndPost(holder.lockCanvas().apply {
+ drawColor(Color.BLUE)
+ val paint = Paint()
+ paint.textSize = 16 * resources.displayMetrics.density
+ paint.textAlign = Paint.Align.CENTER
+ paint.color = Color.WHITE
+ drawText("I'm a setZOrderOnTop(true) SurfaceView!",
+ (width / 2).toFloat(), (height / 2).toFloat(), paint)
+ })
+ }
+
+ override fun surfaceDestroyed(p0: SurfaceHolder) {
+
+ }
+
+ })
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt
new file mode 100644
index 000000000000..ccb71ec0ff2a
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 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.test.hwui
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+class ScrollingZAboveSurfaceView : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.scrolling_zabove_surfaceview)
+
+ findViewById<SurfaceView>(R.id.surfaceview).apply {
+ setZOrderOnTop(true)
+ holder.addCallback(object : SurfaceHolder.Callback {
+ override fun surfaceCreated(p0: SurfaceHolder) {
+
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ holder.unlockCanvasAndPost(holder.lockCanvas().apply {
+ drawColor(Color.BLUE)
+ val paint = Paint()
+ paint.textSize = 16 * resources.displayMetrics.density
+ paint.textAlign = Paint.Align.CENTER
+ paint.color = Color.WHITE
+ drawText("I'm a setZOrderOnTop(true) SurfaceView!",
+ (width / 2).toFloat(), (height / 2).toFloat(), paint)
+ })
+ }
+
+ override fun surfaceDestroyed(p0: SurfaceHolder) {
+
+ }
+
+ })
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/graphics/SilkFX/res/layout/view_blur_behind.xml b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml
new file mode 100644
index 000000000000..83b1fa4b73cb
--- /dev/null
+++ b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="I'm a little teapot" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="Something. Something." />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="I'm a little teapot" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="Something. Something." />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:textSize="24dp"
+ android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" />
+
+ </LinearLayout>
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="300dp" />
+
+ <com.android.test.silkfx.materials.BlurBehindContainer
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="#33AAAAAA"
+ android:padding="32dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="48dp"
+ android:text="Blur!" />
+
+ </com.android.test.silkfx.materials.BlurBehindContainer>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1024dp" />
+
+ </LinearLayout>
+
+ </ScrollView>
+
+</FrameLayout> \ No newline at end of file
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
index 59a6078376cf..6b6d3b8d3d12 100644
--- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -61,7 +61,8 @@ private val AllDemos = listOf(
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
- Demo("Background Blur", BackgroundBlurActivity::class)
+ Demo("Background Blur", BackgroundBlurActivity::class),
+ Demo("View blur behind", R.layout.view_blur_behind, commonControls = false)
))
)
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt
new file mode 100644
index 000000000000..ce6348e32969
--- /dev/null
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.test.silkfx.materials
+
+import android.content.Context
+import android.graphics.RenderEffect
+import android.graphics.Shader
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+class BlurBehindContainer(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) {
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ setBackdropRenderEffect(
+ RenderEffect.createBlurEffect(16.0f, 16.0f, Shader.TileMode.CLAMP))
+ }
+} \ No newline at end of file
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index b68937f268e2..44aa4028c916 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -23,6 +23,7 @@ android_test {
resource_dirs: ["res"],
libs: ["android.test.runner.stubs"],
static_libs: [
+ "androidx.core_core",
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index fff1dd1a7cb1..5f9a710c5f78 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -16,25 +16,40 @@
package com.android.server.inputmethod.multisessiontest;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.getResponderUserId;
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.launchActivityAsUserSync;
import static com.android.compatibility.common.util.concurrentuser.ConcurrentUserActivityUtils.sendBundleAndWaitForReply;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_EDITTEXT_CENTER;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_IME_SHOWN;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_EDITTEXT_POSITION;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_HIDE_IME;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_SHOW_IME;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.UiAutomation;
import android.content.ComponentName;
+import android.content.Context;
import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
import androidx.test.core.app.ActivityScenario;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
@@ -44,8 +59,10 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.util.List;
+
@RunWith(BedsteadJUnit4.class)
-@Ignore("b/345557347")
public final class ConcurrentMultiUserTest {
@ClassRule
@@ -55,6 +72,10 @@ public final class ConcurrentMultiUserTest {
private static final ComponentName TEST_ACTIVITY = new ComponentName(
getInstrumentation().getTargetContext().getPackageName(),
MainActivity.class.getName());
+ private final Context mContext = getInstrumentation().getTargetContext();
+ private final InputMethodManager mInputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
+ private final UiAutomation mUiAutomation = getInstrumentation().getUiAutomation();
private ActivityScenario<MainActivity> mActivityScenario;
private MainActivity mActivity;
@@ -69,17 +90,19 @@ public final class ConcurrentMultiUserTest {
// Launch driver activity.
mActivityScenario = ActivityScenario.launch(MainActivity.class);
mActivityScenario.onActivity(activity -> mActivity = activity);
+ mUiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_USERS_FULL);
}
@After
public void tearDown() {
+ mUiAutomation.dropShellPermissionIdentity();
if (mActivityScenario != null) {
mActivityScenario.close();
}
}
@Test
- public void driverShowImeNotAffectPassenger() {
+ public void driverShowImeNotAffectPassenger() throws Exception {
assertDriverImeHidden();
assertPassengerImeHidden();
@@ -87,6 +110,91 @@ public final class ConcurrentMultiUserTest {
assertPassengerImeHidden();
}
+ @Test
+ @Ignore("b/352823913")
+ public void passengerShowImeNotAffectDriver() throws Exception {
+ assertDriverImeHidden();
+ assertPassengerImeHidden();
+
+ showPassengerImeAndAssert();
+ assertDriverImeHidden();
+ }
+
+ @Test
+ public void driverHideImeNotAffectPassenger() throws Exception {
+ showDriverImeAndAssert();
+ showPassengerImeAndAssert();
+
+ hideDriverImeAndAssert();
+ assertPassengerImeShown();
+ }
+
+ @Test
+ public void passengerHideImeNotAffectDriver() throws Exception {
+ showDriverImeAndAssert();
+ showPassengerImeAndAssert();
+
+ hidePassengerImeAndAssert();
+ assertDriverImeShown();
+ }
+
+ @Test
+ public void imeListNotEmpty() {
+ List<InputMethodInfo> driverImeList = mInputMethodManager.getInputMethodList();
+ assertWithMessage("Driver IME list shouldn't be empty")
+ .that(driverImeList.isEmpty()).isFalse();
+
+ List<InputMethodInfo> passengerImeList =
+ mInputMethodManager.getInputMethodListAsUser(mPeerUserId);
+ assertWithMessage("Passenger IME list shouldn't be empty")
+ .that(passengerImeList.isEmpty()).isFalse();
+ }
+
+ @Test
+ public void enabledImeListNotEmpty() {
+ List<InputMethodInfo> driverEnabledImeList =
+ mInputMethodManager.getEnabledInputMethodList();
+ assertWithMessage("Driver enabled IME list shouldn't be empty")
+ .that(driverEnabledImeList.isEmpty()).isFalse();
+
+ List<InputMethodInfo> passengerEnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(mPeerUserId));
+ assertWithMessage("Passenger enabled IME list shouldn't be empty")
+ .that(passengerEnabledImeList.isEmpty()).isFalse();
+ }
+
+ @Test
+ public void currentImeNotNull() {
+ InputMethodInfo driverIme = mInputMethodManager.getCurrentInputMethodInfo();
+ assertWithMessage("Driver IME shouldn't be null").that(driverIme).isNotNull();
+
+ InputMethodInfo passengerIme =
+ mInputMethodManager.getCurrentInputMethodInfoAsUser(UserHandle.of(mPeerUserId));
+ assertWithMessage("Passenger IME shouldn't be null")
+ .that(passengerIme).isNotNull();
+ }
+
+ @Test
+ public void enableDisableImePerUser() throws IOException {
+ UserHandle driver = UserHandle.of(mContext.getUserId());
+ UserHandle passenger = UserHandle.of(mPeerUserId);
+ enableDisableImeForUser(driver, passenger);
+ enableDisableImeForUser(passenger, driver);
+ }
+
+ @Test
+ public void setImePerUser() throws IOException {
+ UserHandle driver = UserHandle.of(mContext.getUserId());
+ UserHandle passenger = UserHandle.of(mPeerUserId);
+ setImeForUser(driver, passenger);
+ setImeForUser(passenger, driver);
+ }
+
+ private void assertDriverImeShown() {
+ assertWithMessage("Driver IME should be shown")
+ .that(mActivity.isMyImeVisible()).isTrue();
+ }
+
private void assertDriverImeHidden() {
assertWithMessage("Driver IME should be hidden")
.that(mActivity.isMyImeVisible()).isFalse();
@@ -98,10 +206,157 @@ public final class ConcurrentMultiUserTest {
Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
mPeerUserId, bundleToSend);
assertWithMessage("Passenger IME should be hidden")
- .that(receivedBundle.getInt(KEY_RESULT_CODE)).isEqualTo(REPLY_IME_HIDDEN);
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN, /* defaultValue= */ true)).isFalse();
}
- private void showDriverImeAndAssert() {
+ private void assertPassengerImeShown() {
+ final Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_IME_STATUS);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ assertWithMessage("Passenger IME should be shown")
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN)).isTrue();
+ }
+
+ private void showDriverImeAndAssert() throws Exception {
+ // WindowManagerInternal only allows the top focused display to show IME, so this method
+ // taps the driver display in case it is not the top focused display.
+ moveDriverDisplayToTop();
+
mActivity.showMyImeAndWait();
}
+
+ private void hideDriverImeAndAssert() {
+ mActivity.hideMyImeAndWait();
+ }
+
+ private void showPassengerImeAndAssert() throws Exception {
+ // WindowManagerInternal only allows the top focused display to show IME, so this method
+ // taps the passenger display in case it is not the top focused display.
+ movePassengerDisplayToTop();
+
+ Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_SHOW_IME);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+
+ assertWithMessage("Passenger IME should be shown")
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN)).isTrue();
+ }
+
+ private void hidePassengerImeAndAssert() {
+ Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_HIDE_IME);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+
+ assertWithMessage("Passenger IME should be hidden")
+ .that(receivedBundle.getBoolean(KEY_IME_SHOWN, /* defaultValue= */ true)).isFalse();
+ }
+
+ private void moveDriverDisplayToTop() throws Exception {
+ float[] driverEditTextCenter = mActivity.getEditTextCenter();
+ SystemUtil.runShellCommand(mUiAutomation, String.format("input tap %f %f",
+ driverEditTextCenter[0], driverEditTextCenter[1]));
+ }
+
+ private void movePassengerDisplayToTop() throws Exception {
+ final Bundle bundleToSend = new Bundle();
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_EDITTEXT_POSITION);
+ Bundle receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ final float[] passengerEditTextCenter = receivedBundle.getFloatArray(KEY_EDITTEXT_CENTER);
+
+ bundleToSend.putInt(KEY_REQUEST_CODE, REQUEST_DISPLAY_ID);
+ receivedBundle = sendBundleAndWaitForReply(TEST_ACTIVITY.getPackageName(),
+ mPeerUserId, bundleToSend);
+ final int passengerDisplayId = receivedBundle.getInt(KEY_DISPLAY_ID);
+ SystemUtil.runShellCommand(mUiAutomation, String.format("input -d %d tap %f %f",
+ passengerDisplayId, passengerEditTextCenter[0], passengerEditTextCenter[1]));
+ }
+
+ /**
+ * Disables/enables IME for {@code user1}, then verifies that the IME settings for {@code user1}
+ * has changed as expected and {@code user2} stays the same.
+ */
+ private void enableDisableImeForUser(UserHandle user1, UserHandle user2) throws IOException {
+ List<InputMethodInfo> user1EnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ List<InputMethodInfo> user2EnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+
+ // Disable an IME for user1.
+ InputMethodInfo imeToDisable = user1EnabledImeList.get(0);
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime disable --user " + user1.getIdentifier() + " " + imeToDisable.getId());
+ List<InputMethodInfo> user1EnabledImeList2 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ List<InputMethodInfo> user2EnabledImeList2 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+ assertWithMessage("User " + user1.getIdentifier() + " IME " + imeToDisable.getId()
+ + " should be disabled")
+ .that(user1EnabledImeList2.contains(imeToDisable)).isFalse();
+ assertWithMessage("Disabling user " + user1.getIdentifier()
+ + " IME shouldn't affect user " + user2.getIdentifier())
+ .that(user2EnabledImeList2.containsAll(user2EnabledImeList)
+ && user2EnabledImeList.containsAll(user2EnabledImeList2))
+ .isTrue();
+
+ // Enable the IME.
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime enable --user " + user1.getIdentifier() + " " + imeToDisable.getId());
+ List<InputMethodInfo> user1EnabledImeList3 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ List<InputMethodInfo> user2EnabledImeList3 =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user2);
+ assertWithMessage("User " + user1.getIdentifier() + " IME " + imeToDisable.getId()
+ + " should be enabled").that(user1EnabledImeList3.contains(imeToDisable)).isTrue();
+ assertWithMessage("Enabling user " + user1.getIdentifier()
+ + " IME shouldn't affect user " + user2.getIdentifier())
+ .that(user2EnabledImeList2.containsAll(user2EnabledImeList3)
+ && user2EnabledImeList3.containsAll(user2EnabledImeList2))
+ .isTrue();
+ }
+
+ /**
+ * Sets/resets IME for {@code user1}, then verifies that the IME settings for {@code user1}
+ * has changed as expected and {@code user2} stays the same.
+ */
+ private void setImeForUser(UserHandle user1, UserHandle user2) throws IOException {
+ // Reset IME for user1.
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime reset --user " + user1.getIdentifier());
+
+ List<InputMethodInfo> user1EnabledImeList =
+ mInputMethodManager.getEnabledInputMethodListAsUser(user1);
+ assumeTrue("There must be at least two IME to test", user1EnabledImeList.size() >= 2);
+ InputMethodInfo user1Ime = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+ InputMethodInfo user2Ime = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+
+ // Set to another IME for user1.
+ InputMethodInfo anotherIme = null;
+ for (InputMethodInfo info : user1EnabledImeList) {
+ if (!info.equals(user1Ime)) {
+ anotherIme = info;
+ }
+ }
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime set --user " + user1.getIdentifier() + " " + anotherIme.getId());
+ InputMethodInfo user1Ime2 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+ InputMethodInfo user2Ime2 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+ assertWithMessage("The current IME for user " + user1.getIdentifier() + " is wrong")
+ .that(user1Ime2).isEqualTo(anotherIme);
+ assertWithMessage("The current IME for user " + user2.getIdentifier() + " shouldn't change")
+ .that(user2Ime2).isEqualTo(user2Ime);
+
+ // Reset IME for user1.
+ SystemUtil.runShellCommand(mUiAutomation,
+ "ime reset --user " + user1.getIdentifier());
+ InputMethodInfo user1Ime3 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user1);
+ InputMethodInfo user2Ime3 = mInputMethodManager.getCurrentInputMethodInfoAsUser(user2);
+ assertWithMessage("The current IME for user " + user1.getIdentifier() + " is wrong")
+ .that(user1Ime3).isEqualTo(user1Ime);
+ assertWithMessage("The current IME for user " + user2.getIdentifier() + " shouldn't change")
+ .that(user2Ime3).isEqualTo(user2Ime);
+ }
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
index f1260008ca59..fa0aa19a8822 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java
@@ -16,20 +16,25 @@
package com.android.server.inputmethod.multisessiontest;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_EDITTEXT_CENTER;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_IME_SHOWN;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_REQUEST_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.KEY_RESULT_CODE;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_HIDDEN;
-import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REPLY_IME_SHOWN;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_DISPLAY_ID;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_EDITTEXT_POSITION;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_HIDE_IME;
import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_IME_STATUS;
+import static com.android.server.inputmethod.multisessiontest.TestRequestConstants.REQUEST_SHOW_IME;
import android.app.Activity;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
-import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import androidx.annotation.WorkerThread;
import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import com.android.compatibility.common.util.PollingCheck;
@@ -43,7 +48,6 @@ public final class MainActivity extends ConcurrentUserActivityBase {
private static final long WAIT_IME_TIMEOUT_MS = 3000;
private EditText mEditor;
- private InputMethodManager mImm;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -52,19 +56,56 @@ public final class MainActivity extends ConcurrentUserActivityBase {
+ Process.myUserHandle().getIdentifier() + " on display "
+ getDisplay().getDisplayId());
setContentView(R.layout.main_activity);
- mImm = getSystemService(InputMethodManager.class);
mEditor = requireViewById(R.id.edit_text);
}
@Override
+ protected void onResume() {
+ super.onResume();
+ Log.v(TAG, "onResume");
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Log.v(TAG, "onPause");
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.v(TAG, "onResume");
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ Log.v(TAG, "onWindowFocusChanged " + hasFocus);
+ }
+
+ @Override
+ @WorkerThread
protected Bundle onBundleReceived(Bundle receivedBundle) {
final int requestCode = receivedBundle.getInt(KEY_REQUEST_CODE);
Log.v(TAG, "onBundleReceived() with request code:" + requestCode);
final Bundle replyBundle = new Bundle();
switch (requestCode) {
case REQUEST_IME_STATUS:
- replyBundle.putInt(KEY_RESULT_CODE,
- isMyImeVisible() ? REPLY_IME_SHOWN : REPLY_IME_HIDDEN);
+ replyBundle.putBoolean(KEY_IME_SHOWN, isMyImeVisible());
+ break;
+ case REQUEST_SHOW_IME:
+ showMyImeAndWait();
+ replyBundle.putBoolean(KEY_IME_SHOWN, isMyImeVisible());
+ break;
+ case REQUEST_HIDE_IME:
+ hideMyImeAndWait();
+ replyBundle.putBoolean(KEY_IME_SHOWN, isMyImeVisible());
+ break;
+ case REQUEST_EDITTEXT_POSITION:
+ replyBundle.putFloatArray(KEY_EDITTEXT_CENTER, getEditTextCenter());
+ break;
+ case REQUEST_DISPLAY_ID:
+ replyBundle.putInt(KEY_DISPLAY_ID, getDisplay().getDisplayId());
break;
default:
throw new RuntimeException("Received undefined request code:" + requestCode);
@@ -77,21 +118,41 @@ public final class MainActivity extends ConcurrentUserActivityBase {
return insets == null ? false : insets.isVisible(WindowInsetsCompat.Type.ime());
}
+ float[] getEditTextCenter() {
+ final float editTextCenterX = mEditor.getX() + 0.5f * mEditor.getWidth();
+ final float editTextCenterY = mEditor.getY() + 0.5f * mEditor.getHeight();
+ return new float[]{editTextCenterX, editTextCenterY};
+ }
+
+ @WorkerThread
void showMyImeAndWait() {
- Log.v(TAG, "showSoftInput");
runOnUiThread(() -> {
- // requestFocus() must run on UI thread.
+ // View#requestFocus() and WindowInsetsControllerCompat#show() must run on UI thread.
if (!mEditor.requestFocus()) {
Log.e(TAG, "Failed to focus on mEditor");
return;
}
- if (!mImm.showSoftInput(mEditor, /* flags= */ 0)) {
- Log.e(TAG, String.format("Failed to show my IME as user %d, "
- + "mEditor:focused=%b,hasWindowFocus=%b", getUserId(),
- mEditor.isFocused(), mEditor.hasWindowFocus()));
- }
+ // Compared to mImm.showSoftInput(), the call below is the recommended way to show the
+ // keyboard because it is guaranteed to be scheduled after the window is focused.
+ Log.v(TAG, "showSoftInput");
+ WindowCompat.getInsetsController(getWindow(), mEditor).show(
+ WindowInsetsCompat.Type.ime());
});
PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> isMyImeVisible(),
- String.format("My IME (user %d) didn't show up", getUserId()));
+ String.format("%s: My IME (user %d) didn't show up", TAG,
+ Process.myUserHandle().getIdentifier()));
+ }
+
+ @WorkerThread
+ void hideMyImeAndWait() {
+ runOnUiThread(() -> {
+ Log.v(TAG, "hideSoftInput");
+ // WindowInsetsControllerCompat#hide() must run on UI thread.
+ WindowCompat.getInsetsController(getWindow(), mEditor)
+ .hide(WindowInsetsCompat.Type.ime());
+ });
+ PollingCheck.waitFor(WAIT_IME_TIMEOUT_MS, () -> !isMyImeVisible(),
+ String.format("%s: My IME (user %d) is still shown", TAG,
+ Process.myUserHandle().getIdentifier()));
}
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
index 1501bfb69c92..68c9d5403c0b 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/TestRequestConstants.java
@@ -21,9 +21,13 @@ final class TestRequestConstants {
}
public static final String KEY_REQUEST_CODE = "key_request_code";
- public static final String KEY_RESULT_CODE = "key_result_code";
+ public static final String KEY_EDITTEXT_CENTER = "key_edittext_center";
+ public static final String KEY_DISPLAY_ID = "key_display_id";
+ public static final String KEY_IME_SHOWN = "key_ime_shown";
public static final int REQUEST_IME_STATUS = 1;
- public static final int REPLY_IME_SHOWN = 2;
- public static final int REPLY_IME_HIDDEN = 3;
+ public static final int REQUEST_SHOW_IME = 2;
+ public static final int REQUEST_HIDE_IME = 3;
+ public static final int REQUEST_EDITTEXT_POSITION = 4;
+ public static final int REQUEST_DISPLAY_ID = 5;
}
diff --git a/tests/testables/Android.bp b/tests/testables/Android.bp
index 7596ee722d01..17cc0b2a5884 100644
--- a/tests/testables/Android.bp
+++ b/tests/testables/Android.bp
@@ -25,11 +25,18 @@ package {
java_library {
name: "testables",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
libs: [
"android.test.runner.stubs.system",
"android.test.mock.stubs.system",
"androidx.test.rules",
"mockito-target-inline-minus-junit4",
],
+ static_libs: [
+ "PlatformMotionTesting",
+ "kotlinx_coroutines_test",
+ ],
}
diff --git a/tests/testables/src/android/animation/AnimatorTestRule.java b/tests/testables/src/android/animation/AnimatorTestRule.java
new file mode 100644
index 000000000000..3b39e1fc6bc7
--- /dev/null
+++ b/tests/testables/src/android/animation/AnimatorTestRule.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2023 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 android.animation;
+
+import android.animation.AnimationHandler.AnimationFrameCallback;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunnableWithException;
+import android.util.AndroidRuntimeException;
+import android.util.Singleton;
+import android.view.Choreographer;
+import android.view.animation.AnimationUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * JUnit {@link TestRule} that can be used to run {@link Animator}s without actually waiting for the
+ * duration of the animation. This also helps the test to be written in a deterministic manner.
+ *
+ * Create an instance of {@code AnimatorTestRule} and specify it as a {@link org.junit.Rule}
+ * of the test class. Use {@link #advanceTimeBy(long)} to advance animators that have been started.
+ * Note that {@link #advanceTimeBy(long)} should be called from the same thread you have used to
+ * start the animator.
+ *
+ * <pre>
+ * {@literal @}SmallTest
+ * {@literal @}RunWith(AndroidJUnit4.class)
+ * public class SampleAnimatorTest {
+ *
+ * {@literal @}Rule
+ * public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ *
+ * {@literal @}UiThreadTest
+ * {@literal @}Test
+ * public void sample() {
+ * final ValueAnimator animator = ValueAnimator.ofInt(0, 1000);
+ * animator.setDuration(1000L);
+ * assertThat(animator.getAnimatedValue(), is(0));
+ * animator.start();
+ * mAnimatorTestRule.advanceTimeBy(500L);
+ * assertThat(animator.getAnimatedValue(), is(500));
+ * }
+ * }
+ * </pre>
+ */
+public final class AnimatorTestRule implements TestRule {
+
+ private final Object mLock = new Object();
+ private final Singleton<TestHandler> mTestHandler = new Singleton<>() {
+ @Override
+ protected TestHandler create() {
+ return new TestHandler();
+ }
+ };
+ private final Object mTest;
+ private final long mStartTime;
+ private long mTotalTimeDelta = 0;
+ private volatile boolean mCanLockAnimationClock;
+ private Looper mLooperWithLockedAnimationClock;
+
+ /**
+ * Construct an AnimatorTestRule with access to the test instance and a custom start time.
+ * @see #AnimatorTestRule(Object)
+ */
+ public AnimatorTestRule(Object test, long startTime) {
+ mTest = test;
+ mStartTime = startTime;
+ }
+
+ /**
+ * Construct an AnimatorTestRule for the given test instance with a start time of
+ * {@link SystemClock#uptimeMillis()}. Initializing the start time with this clock reduces the
+ * discrepancies with various internals of classes like ValueAnimator which can sometimes read
+ * that clock via {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}.
+ *
+ * @param test the test instance used to access the {@link TestableLooper} used by the class.
+ */
+ public AnimatorTestRule(Object test) {
+ this(test, SystemClock.uptimeMillis());
+ }
+
+ @NonNull
+ @Override
+ public Statement apply(@NonNull final Statement base, @NonNull Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ final TestHandler testHandler = mTestHandler.get();
+ final AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler);
+ final RunnableWithException lockClock =
+ wrapWithRunBlocking(new LockAnimationClockRunnable());
+ final RunnableWithException unlockClock =
+ wrapWithRunBlocking(new UnlockAnimationClockRunnable());
+ try {
+ lockClock.run();
+ base.evaluate();
+ } finally {
+ unlockClock.run();
+ AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart);
+ if (testHandler != objAtEnd) {
+ // pass or fail, inner logic not restoring the handler needs to be reported.
+ // noinspection ThrowFromFinallyBlock
+ throw new IllegalStateException("Test handler was altered: expected="
+ + testHandler + " actual=" + objAtEnd);
+ }
+ }
+ }
+ };
+ }
+
+ private RunnableWithException wrapWithRunBlocking(RunnableWithException runnable) {
+ RunnableWithException wrapped = TestableLooper.wrapWithRunBlocking(mTest, runnable);
+ if (wrapped != null) {
+ return wrapped;
+ }
+ return () -> runOnMainThrowing(runnable);
+ }
+
+ private static void runOnMainThrowing(RunnableWithException runnable) throws Exception {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ runnable.run();
+ } else {
+ final Throwable[] throwableBox = new Throwable[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ try {
+ runnable.run();
+ } catch (Throwable t) {
+ throwableBox[0] = t;
+ }
+ });
+ if (throwableBox[0] == null) {
+ return;
+ } else if (throwableBox[0] instanceof RuntimeException ex) {
+ throw ex;
+ } else if (throwableBox[0] instanceof Error err) {
+ throw err;
+ } else {
+ throw new RuntimeException(throwableBox[0]);
+ }
+ }
+ }
+
+ private class LockAnimationClockRunnable implements RunnableWithException {
+ @Override
+ public void run() {
+ mLooperWithLockedAnimationClock = Looper.myLooper();
+ mCanLockAnimationClock = true;
+ lockAnimationClockToCurrentTime();
+ }
+ }
+
+ private class UnlockAnimationClockRunnable implements RunnableWithException {
+ @Override
+ public void run() {
+ mCanLockAnimationClock = false;
+ mLooperWithLockedAnimationClock = null;
+ AnimationUtils.unlockAnimationClock();
+ }
+ }
+
+ private void lockAnimationClockToCurrentTime() {
+ if (!mCanLockAnimationClock) {
+ throw new AssertionError("Unable to lock the animation clock; "
+ + "has the test started? already finished?");
+ }
+ if (mLooperWithLockedAnimationClock != Looper.myLooper()) {
+ throw new AssertionError("Animation clock being locked on " + Looper.myLooper()
+ + " but should only be locked on " + mLooperWithLockedAnimationClock);
+ }
+ long desiredTime = getCurrentTime();
+ AnimationUtils.lockAnimationClock(desiredTime);
+ if (!mCanLockAnimationClock) {
+ AnimationUtils.unlockAnimationClock();
+ throw new AssertionError("Threading error when locking the animation clock");
+ }
+ long outputTime = AnimationUtils.currentAnimationTimeMillis();
+ if (outputTime != desiredTime) {
+ // Skip the test (rather than fail it) if there's a clock issue
+ throw new AssumptionViolatedException("currentAnimationTimeMillis() is " + outputTime
+ + " after locking to " + desiredTime);
+ }
+ }
+
+ /**
+ * If any new {@link Animator}s have been registered since the last time the frame time was
+ * advanced, initialize them with the current frame time. Failing to do this will result in the
+ * animations beginning on the *next* advancement instead, so this is done automatically for
+ * test authors inside of {@link #advanceTimeBy}. However this is exposed in case authors want
+ * to validate operations performed by onStart listeners.
+ * <p>
+ * NOTE: This is only required of the platform ValueAnimator because its start() method calls
+ * {@link AnimationHandler#addAnimationFrameCallback} BEFORE it calls startAnimation(), so this
+ * rule can't synchronously trigger the callback at that time.
+ */
+ public void initNewAnimators() {
+ requireLooper("AnimationTestRule#initNewAnimators()");
+ long currentTime = getCurrentTime();
+ final TestHandler testHandler = mTestHandler.get();
+ List<AnimationFrameCallback> newCallbacks = new ArrayList<>(testHandler.mNewCallbacks);
+ testHandler.mNewCallbacks.clear();
+ for (AnimationFrameCallback newCallback : newCallbacks) {
+ newCallback.doAnimationFrame(currentTime);
+ }
+ }
+
+ /**
+ * Advances the animation clock by the given amount of delta in milliseconds. This call will
+ * produce an animation frame to all the ongoing animations. This method needs to be
+ * called on the same thread as {@link Animator#start()}.
+ *
+ * @param timeDelta the amount of milliseconds to advance
+ */
+ public void advanceTimeBy(long timeDelta) {
+ advanceTimeBy(timeDelta, null);
+ }
+
+ /**
+ * Advances the animation clock by the given amount of delta in milliseconds. This call will
+ * produce an animation frame to all the ongoing animations. This method needs to be
+ * called on the same thread as {@link Animator#start()}.
+ * <p>
+ * This method is not for test authors, but for rule authors to ensure that multiple animators
+ * can be advanced in sync.
+ *
+ * @param timeDelta the amount of milliseconds to advance
+ * @param preFrameAction a consumer to be passed the timeDelta following the time advancement
+ * but prior to the frame production.
+ */
+ public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) {
+ Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative");
+ requireLooper("AnimationTestRule#advanceTimeBy(long)");
+ final TestHandler testHandler = mTestHandler.get();
+ if (timeDelta == 0) {
+ // If time is not being advanced, all animators will get a tick; don't double tick these
+ testHandler.mNewCallbacks.clear();
+ } else {
+ // before advancing time, start new animators with the current time
+ initNewAnimators();
+ }
+ synchronized (mLock) {
+ // advance time
+ mTotalTimeDelta += timeDelta;
+ }
+ lockAnimationClockToCurrentTime();
+ if (preFrameAction != null) {
+ preFrameAction.accept(timeDelta);
+ // After letting other code run, clear any new callbacks to avoid double-ticking them
+ testHandler.mNewCallbacks.clear();
+ }
+ // produce a frame
+ testHandler.doFrame();
+ }
+
+ /**
+ * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
+ * different time than the time tracked by {@link SystemClock} This method needs to be called on
+ * the same thread as {@link Animator#start()}.
+ */
+ public long getCurrentTime() {
+ requireLooper("AnimationTestRule#getCurrentTime()");
+ synchronized (mLock) {
+ return mStartTime + mTotalTimeDelta;
+ }
+ }
+
+ private static void requireLooper(String method) {
+ if (Looper.myLooper() == null) {
+ throw new AndroidRuntimeException(method + " may only be called on Looper threads");
+ }
+ }
+
+ private class TestHandler extends AnimationHandler {
+ public final TestProvider mTestProvider = new TestProvider();
+ private final List<AnimationFrameCallback> mNewCallbacks = new ArrayList<>();
+
+ TestHandler() {
+ setProvider(mTestProvider);
+ }
+
+ public void doFrame() {
+ mTestProvider.animateFrame();
+ mTestProvider.commitFrame();
+ }
+
+ @Override
+ public void addAnimationFrameCallback(AnimationFrameCallback callback, long delay) {
+ // NOTE: using the delay is infeasible because the AnimationHandler uses
+ // SystemClock.uptimeMillis(); -- If we fix this to use an overridable method, then we
+ // could fix this for tests.
+ super.addAnimationFrameCallback(callback, 0);
+ if (delay <= 0) {
+ mNewCallbacks.add(callback);
+ }
+ }
+
+ @Override
+ public void removeCallback(AnimationFrameCallback callback) {
+ super.removeCallback(callback);
+ mNewCallbacks.remove(callback);
+ }
+ }
+
+ private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
+ private long mFrameDelay = 10;
+ private Choreographer.FrameCallback mFrameCallback = null;
+ private final List<Runnable> mCommitCallbacks = new ArrayList<>();
+
+ public void animateFrame() {
+ Choreographer.FrameCallback frameCallback = mFrameCallback;
+ mFrameCallback = null;
+ if (frameCallback != null) {
+ frameCallback.doFrame(getFrameTime());
+ }
+ }
+
+ public void commitFrame() {
+ List<Runnable> commitCallbacks = new ArrayList<>(mCommitCallbacks);
+ mCommitCallbacks.clear();
+ for (Runnable commitCallback : commitCallbacks) {
+ commitCallback.run();
+ }
+ }
+
+ @Override
+ public void postFrameCallback(Choreographer.FrameCallback callback) {
+ assert mFrameCallback == null;
+ mFrameCallback = callback;
+ }
+
+ @Override
+ public void postCommitCallback(Runnable runnable) {
+ mCommitCallbacks.add(runnable);
+ }
+
+ @Override
+ public void setFrameDelay(long delay) {
+ mFrameDelay = delay;
+ }
+
+ @Override
+ public long getFrameDelay() {
+ return mFrameDelay;
+ }
+
+ @Override
+ public long getFrameTime() {
+ return getCurrentTime();
+ }
+ }
+}
diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
new file mode 100644
index 000000000000..ded467993eef
--- /dev/null
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 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 android.animation
+
+import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.view.View
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ActivityScenario
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import platform.test.motion.MotionTestRule
+import platform.test.motion.RecordedMotion
+import platform.test.motion.RecordedMotion.Companion.create
+import platform.test.motion.golden.DataPoint
+import platform.test.motion.golden.Feature
+import platform.test.motion.golden.FrameId
+import platform.test.motion.golden.TimeSeries
+import platform.test.motion.golden.TimeSeriesCaptureScope
+import platform.test.motion.golden.TimestampFrameId
+import platform.test.screenshot.captureToBitmapAsync
+
+class AnimatorTestRuleToolkit(
+ internal val animatorTestRule: AnimatorTestRule,
+ internal val testScope: TestScope,
+ internal val currentActivityScenario: () -> ActivityScenario<*>,
+) {
+ internal companion object {
+ const val TAG = "AnimatorRuleToolkit"
+ }
+}
+
+/** Capture utility to extract a [Bitmap] from a [drawable]. */
+fun captureDrawable(drawable: Drawable): Bitmap {
+ val width = drawable.bounds.right - drawable.bounds.left
+ val height = drawable.bounds.bottom - drawable.bounds.top
+
+ // If either dimension is 0 this will fail, so we set it to 1 pixel instead.
+ return drawable.toBitmap(
+ width =
+ if (width > 0) {
+ width
+ } else {
+ 1
+ },
+ height =
+ if (height > 0) {
+ height
+ } else {
+ 1
+ },
+ )
+}
+
+/** Capture utility to extract a [Bitmap] from a [view]. */
+fun captureView(view: View): Bitmap {
+ return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS)
+}
+
+/**
+ * Controls the timing of the motion recording.
+ *
+ * The time series is recorded while the [recording] function is running.
+ */
+class MotionControl(val recording: MotionControlFn)
+
+typealias MotionControlFn = suspend MotionControlScope.() -> Unit
+
+interface MotionControlScope {
+ /** Waits until [check] returns true. Invoked on each frame. */
+ suspend fun awaitCondition(check: () -> Boolean)
+
+ /** Waits for [count] frames to be processed. */
+ suspend fun awaitFrames(count: Int = 1)
+}
+
+/** Defines the sampling of features during a test run. */
+data class AnimatorRuleRecordingSpec<T>(
+ /** The root `observing` object, available in [timeSeriesCapture]'s [TimeSeriesCaptureScope]. */
+ val captureRoot: T,
+
+ /** The timing for the recording. */
+ val motionControl: MotionControl,
+
+ /** Time interval between frame captures, in milliseconds. */
+ val frameDurationMs: Long = 16L,
+
+ /** Whether a sequence of screenshots should also be recorded. */
+ val visualCapture: ((captureRoot: T) -> Bitmap)? = null,
+
+ /** Produces the time-series, invoked on each animation frame. */
+ val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit,
+)
+
+/** Records the time-series of the features specified in [recordingSpec]. */
+fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
+ recordingSpec: AnimatorRuleRecordingSpec<T>
+): RecordedMotion {
+ with(toolkit.animatorTestRule) {
+ val activityScenario = toolkit.currentActivityScenario()
+ val frameIdCollector = mutableListOf<FrameId>()
+ val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+ val screenshotCollector =
+ if (recordingSpec.visualCapture != null) {
+ mutableListOf<Bitmap>()
+ } else {
+ null
+ }
+
+ fun recordFrame(frameId: FrameId) {
+ Log.i(TAG, "recordFrame($frameId)")
+ frameIdCollector.add(frameId)
+ activityScenario.onActivity {
+ recordingSpec.timeSeriesCapture.invoke(
+ TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+ )
+ }
+
+ val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot)
+ if (bitmap != null) screenshotCollector!!.add(bitmap)
+ }
+
+ val motionControl =
+ MotionControlImpl(
+ toolkit.animatorTestRule,
+ toolkit.testScope,
+ recordingSpec.frameDurationMs,
+ recordingSpec.motionControl,
+ )
+
+ Log.i(TAG, "recordMotion() begin recording")
+
+ var startFrameTime: Long? = null
+ toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime }
+ while (!motionControl.recordingEnded) {
+ var time: Long? = null
+ toolkit.currentActivityScenario().onActivity { time = currentTime }
+ recordFrame(TimestampFrameId(time!! - startFrameTime!!))
+ toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() }
+ }
+
+ Log.i(TAG, "recordMotion() end recording")
+
+ val timeSeries =
+ TimeSeries(
+ frameIdCollector.toList(),
+ propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
+ )
+
+ return create(timeSeries, screenshotCollector)
+ }
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+private class MotionControlImpl(
+ val animatorTestRule: AnimatorTestRule,
+ val testScope: TestScope,
+ val frameMs: Long,
+ motionControl: MotionControl,
+) : MotionControlScope {
+ private val recordingJob = motionControl.recording.launch()
+
+ private val frameEmitter = MutableStateFlow<Long>(0)
+ private val onFrame = frameEmitter.asStateFlow()
+
+ var recordingEnded: Boolean = false
+
+ fun nextFrame() {
+ animatorTestRule.advanceTimeBy(frameMs)
+
+ frameEmitter.tryEmit(animatorTestRule.currentTime)
+ testScope.runCurrent()
+
+ if (recordingJob.isCompleted) {
+ recordingEnded = true
+ }
+ }
+
+ override suspend fun awaitCondition(check: () -> Boolean) {
+ onFrame.takeWhile { !check() }.collect {}
+ }
+
+ override suspend fun awaitFrames(count: Int) {
+ onFrame.take(count).collect {}
+ }
+
+ private fun MotionControlFn.launch(): Job {
+ val function = this
+ return testScope.launch { function() }
+ }
+}
diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java
index 37b39c314e53..6a8e142e2314 100644
--- a/tests/testables/src/android/testing/TestWithLooperRule.java
+++ b/tests/testables/src/android/testing/TestWithLooperRule.java
@@ -34,13 +34,13 @@ import java.util.List;
* Looper for the Statement.
*/
public class TestWithLooperRule implements MethodRule {
-
/*
* This rule requires to be the inner most Rule, so the next statement is RunAfters
* instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)'
*/
@Override
public Statement apply(Statement base, FrameworkMethod method, Object target) {
+
// getting testRunner check, if AndroidTestingRunning then we skip this rule
RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class);
if (runWithAnnotation != null) {
@@ -97,6 +97,12 @@ public class TestWithLooperRule implements MethodRule {
case "InvokeParameterizedMethod":
this.wrapFieldMethodFor(next, "frameworkMethod", method, target);
return;
+ case "ExpectException":
+ next = this.getNextStatement(next, "next");
+ break;
+ case "UiThreadStatement":
+ next = this.getNextStatement(next, "base");
+ break;
default:
throw new Exception(
String.format("Unexpected Statement received: [%s]",
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index be5c84c0353c..ac96ef28f501 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -53,6 +53,7 @@ public class TestableLooper {
private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
private static final Field MESSAGE_NEXT_FIELD;
private static final Field MESSAGE_WHEN_FIELD;
+ private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null;
private Looper mLooper;
private MessageQueue mQueue;
@@ -63,6 +64,14 @@ public class TestableLooper {
static {
try {
+ MESSAGE_QUEUE_USE_CONCURRENT_FIELD =
+ MessageQueue.class.getDeclaredField("mUseConcurrent");
+ MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true);
+ } catch (NoSuchFieldException ignored) {
+ // Ignore - maybe this is not CombinedMessageQueue?
+ }
+
+ try {
MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
@@ -146,6 +155,15 @@ public class TestableLooper {
mLooper = l;
mQueue = mLooper.getQueue();
mHandler = new Handler(mLooper);
+
+ // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
+ if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) {
+ try {
+ MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
/**
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 2a3e4ae0c039..f0cda535b3aa 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -26,14 +26,24 @@ android_test {
platform_apis: true,
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src/**/I*.aidl",
],
+ asset_dirs: ["goldens"],
resource_dirs: ["res"],
static_libs: [
+ "PlatformMotionTesting",
+ "androidx.core_core-animation",
+ "androidx.core_core-ktx",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"hamcrest-library",
+ "kotlinx_coroutines_test",
"mockito-target-inline-minus-junit4",
+ "platform-screenshot-diff-core",
+ "platform-test-annotations",
"testables",
+ "truth",
],
compile_multilib: "both",
jni_libs: [
@@ -46,6 +56,7 @@ android_test {
"android.test.mock.stubs.system",
],
certificate: "platform",
+ test_config: "AndroidTest.xml",
test_suites: [
"device-tests",
"automotive-tests",
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04fdb765..6cba59872710 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -23,6 +23,10 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 000000000000..85f6e6257770
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<configuration description="Runs Tests for Testables.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TestablesTests.apk" />
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="screen-always-on" value="on" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="TestableTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.testables" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="test-filter-dir" value="/data/data/com.android.testables" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.testables/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
new file mode 100644
index 000000000000..9aed2e970239
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
new file mode 100644
index 000000000000..1d0c0c3c3393
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
new file mode 100644
index 000000000000..73eb6c74fee6
--- /dev/null
+++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
@@ -0,0 +1,64 @@
+{
+ "frame_ids": [
+ 0,
+ 20,
+ 40,
+ 60,
+ 80,
+ 100,
+ 120,
+ 140,
+ 160,
+ 180,
+ 200,
+ 220,
+ 240,
+ 260,
+ 280,
+ 300,
+ 320,
+ 340,
+ 360,
+ 380,
+ 400,
+ 420,
+ 440,
+ 460,
+ 480,
+ 500
+ ],
+ "features": [
+ {
+ "name": "alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 0.9960574,
+ 0.98429155,
+ 0.9648882,
+ 0.9381534,
+ 0.9045085,
+ 0.8644843,
+ 0.818712,
+ 0.76791346,
+ 0.7128896,
+ 0.65450853,
+ 0.5936906,
+ 0.5313952,
+ 0.46860474,
+ 0.40630943,
+ 0.34549147,
+ 0.2871104,
+ 0.23208654,
+ 0.181288,
+ 0.13551569,
+ 0.09549153,
+ 0.061846733,
+ 0.035111785,
+ 0.015708387,
+ 0.003942609,
+ 0
+ ]
+ }
+ ]
+}
diff --git a/tests/testables/tests/goldens/recordTimeSeries_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
new file mode 100644
index 000000000000..2b97bad08e00
--- /dev/null
+++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
@@ -0,0 +1,48 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272
+ ],
+ "features": [
+ {
+ "name": "alpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 0.9488604,
+ 0.83574325,
+ 0.7016156,
+ 0.5691678,
+ 0.4497436,
+ 0.34789434,
+ 0.26431116,
+ 0.19766562,
+ 0.14572789,
+ 0.10601636,
+ 0.076149896,
+ 0.05401709,
+ 0.037837274,
+ 0.026161024,
+ 0.017839976,
+ 0.011983856,
+ 0.007914998
+ ]
+ }
+ ]
+}
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
new file mode 100644
index 000000000000..5abebee77d3d
--- /dev/null
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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 android.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.core.animation.doOnEnd
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class validates that two tests' animators are isolated from each other when using the
+ * same animator test rule. This is a test to prevent future instances of b/275602127.
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class AnimatorTestRuleIsolationTest {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+ @Test
+ fun testA() {
+ // GIVEN global state is reset at the start of the test
+ didTouchA = false
+ didTouchB = false
+ // WHEN starting 2 animations of different durations, and setting didTouch{A,B} at the end
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 100
+ doOnEnd { didTouchA = true }
+ start()
+ }
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 150
+ doOnEnd { didTouchB = true }
+ start()
+ }
+ // WHEN when you advance time so that only one of the animations has ended
+ animatorTestRule.advanceTimeBy(100)
+ // VERIFY we did indeed end the current animation
+ assertThat(didTouchA).isTrue()
+ // VERIFY advancing the animator did NOT cause testB's animator to end
+ assertThat(didTouchB).isFalse()
+ }
+
+ @Test
+ fun testB() {
+ // GIVEN global state is reset at the start of the test
+ didTouchA = false
+ didTouchB = false
+ // WHEN starting 2 animations of different durations, and setting didTouch{A,B} at the end
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 100
+ doOnEnd { didTouchB = true }
+ start()
+ }
+ ObjectAnimator.ofFloat(0f, 1f).apply {
+ duration = 150
+ doOnEnd { didTouchA = true }
+ start()
+ }
+ animatorTestRule.advanceTimeBy(100)
+ // VERIFY advancing the animator did NOT cause testA's animator to end
+ assertThat(didTouchA).isFalse()
+ // VERIFY we did indeed end the current animation
+ assertThat(didTouchB).isTrue()
+ }
+
+ companion object {
+ var didTouchA = false
+ var didTouchB = false
+ }
+}
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
new file mode 100644
index 000000000000..9eeaad5cd272
--- /dev/null
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 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 android.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.animation.LinearInterpolator
+import androidx.core.animation.doOnEnd
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class AnimatorTestRulePrecisionTest {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+ var value1: Float = -1f
+ var value2: Float = -1f
+
+ private inline fun animateThis(
+ propertyName: String,
+ duration: Long,
+ startDelay: Long = 0,
+ crossinline onEndAction: (animator: Animator) -> Unit,
+ ) {
+ ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
+ it.interpolator = LINEAR_INTERPOLATOR
+ it.duration = duration
+ it.startDelay = startDelay
+ it.doOnEnd(onEndAction)
+ it.start()
+ }
+ }
+
+ @Test
+ fun testSingleAnimator() {
+ var ended = false
+ animateThis("value1", duration = 100) { ended = true }
+
+ assertThat(value1).isEqualTo(0f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(50)
+ assertThat(value1).isEqualTo(0.5f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(49)
+ assertThat(value1).isEqualTo(0.99f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(ended).isTrue()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+ }
+
+ @Test
+ fun testDelayedAnimator() {
+ var ended = false
+ animateThis("value1", duration = 100, startDelay = 50) { ended = true }
+
+ assertThat(value1).isEqualTo(-1f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(49)
+ assertThat(value1).isEqualTo(-1f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(0f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(99)
+ assertThat(value1).isEqualTo(0.99f)
+ assertThat(ended).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(ended).isTrue()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+ }
+
+ @Test
+ fun testTwoAnimators() {
+ var ended1 = false
+ var ended2 = false
+ animateThis("value1", duration = 100) { ended1 = true }
+ animateThis("value2", duration = 200) { ended2 = true }
+ assertThat(value1).isEqualTo(0f)
+ assertThat(value2).isEqualTo(0f)
+ assertThat(ended1).isFalse()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(2)
+
+ animatorTestRule.advanceTimeBy(99)
+ assertThat(value1).isEqualTo(0.99f)
+ assertThat(value2).isEqualTo(0.495f)
+ assertThat(ended1).isFalse()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(2)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(value2).isEqualTo(0.5f)
+ assertThat(ended1).isTrue()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(99)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(value2).isEqualTo(0.995f)
+ assertThat(ended1).isTrue()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(value2).isEqualTo(1f)
+ assertThat(ended1).isTrue()
+ assertThat(ended2).isTrue()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+ }
+
+ @Test
+ fun testChainedAnimators() {
+ var ended1 = false
+ var ended2 = false
+ animateThis("value1", duration = 100) {
+ ended1 = true
+ animateThis("value2", duration = 100) { ended2 = true }
+ }
+
+ assertThat(value1).isEqualTo(0f)
+ assertThat(value2).isEqualTo(-1f)
+ assertThat(ended1).isFalse()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(99)
+ assertThat(value1).isEqualTo(0.99f)
+ assertThat(value2).isEqualTo(-1f)
+ assertThat(ended1).isFalse()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(value2).isEqualTo(0f)
+ assertThat(ended1).isTrue()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(99)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(value2).isEqualTo(0.99f)
+ assertThat(ended1).isTrue()
+ assertThat(ended2).isFalse()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+ animatorTestRule.advanceTimeBy(1)
+ assertThat(value1).isEqualTo(1f)
+ assertThat(value2).isEqualTo(1f)
+ assertThat(ended1).isTrue()
+ assertThat(ended2).isTrue()
+ assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+ }
+
+ private companion object {
+ private val LINEAR_INTERPOLATOR = LinearInterpolator()
+ }
+}
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
new file mode 100644
index 000000000000..993c3fed9d59
--- /dev/null
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2024 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 android.animation
+
+import android.graphics.Color
+import android.platform.test.annotations.MotionTest
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.dynamicanimation.animation.SpringAnimation
+import com.android.internal.dynamicanimation.animation.SpringForce
+import kotlinx.coroutines.test.TestScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.MotionTestRule
+import platform.test.motion.RecordedMotion
+import platform.test.motion.testing.createGoldenPathManager
+import platform.test.motion.view.ViewFeatureCaptures
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.ScreenshotActivity
+import platform.test.screenshot.ScreenshotTestRule
+
+@SmallTest
+@MotionTest
+@RunWith(AndroidJUnit4::class)
+class AnimatorTestRuleToolkitTest {
+ companion object {
+ private val GOLDEN_PATH_MANAGER =
+ createGoldenPathManager("frameworks/base/tests/testables/tests/goldens")
+
+ private val EMULATION_SPEC =
+ DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
+ }
+
+ @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC)
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER)
+ @get:Rule(order = 4)
+ val motionRule =
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario },
+ GOLDEN_PATH_MANAGER,
+ bitmapDiffer = screenshotRule,
+ )
+
+ @Test
+ fun recordFilmstrip_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = true,
+ )
+
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator")
+ }
+
+ @Test
+ fun recordTimeSeries_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = false,
+ )
+
+ motionRule
+ .assertThat(recordedMotion)
+ .timeSeriesMatchesGolden("recordTimeSeries_withAnimator")
+ }
+
+ @Test
+ fun recordFilmstrip_withSpring() {
+ val animatedBox = createScene()
+ var isDone = false
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = true,
+ )
+
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring")
+ }
+
+ @Test
+ fun recordTimeSeries_withSpring() {
+ val animatedBox = createScene()
+ var isDone = false
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = false,
+ )
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring")
+ }
+
+ private fun createScene(): ViewGroup {
+ lateinit var sceneRoot: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }
+ activity.setContentView(sceneRoot)
+ }
+ getInstrumentation().waitForIdleSync()
+ return sceneRoot
+ }
+
+ private fun createAnimator(animatedBox: ViewGroup): AnimatorSet {
+ return AnimatorSet().apply {
+ duration = 500
+ play(
+ ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply {
+ addUpdateListener { animatedBox.alpha = it.animatedValue as Float }
+ }
+ )
+ }
+ }
+
+ private fun createSpring(animatedBox: ViewGroup): SpringAnimation {
+ return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply {
+ spring =
+ SpringForce(0f).apply {
+ stiffness = 500f
+ dampingRatio = 0.95f
+ }
+
+ setStartValue(animatedBox.alpha)
+ setMinValue(0f)
+ setMaxValue(1f)
+ minimumVisibleChange = 0.01f
+ }
+ }
+
+ private fun record(
+ container: ViewGroup,
+ motionControl: MotionControl,
+ sampleIntervalMs: Long,
+ recordScreenshots: Boolean,
+ ): RecordedMotion {
+ val visualCapture =
+ if (recordScreenshots) {
+ ::captureView
+ } else {
+ null
+ }
+ return motionRule.recordMotion(
+ AnimatorRuleRecordingSpec(
+ container,
+ motionControl,
+ sampleIntervalMs,
+ visualCapture,
+ ) {
+ feature(ViewFeatureCaptures.alpha, "alpha")
+ }
+ )
+ }
+}
diff --git a/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java
new file mode 100644
index 000000000000..b7d5e0e12942
--- /dev/null
+++ b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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 android.testing;
+
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that TestableLooper now handles expected exceptions in tests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RunWithLooper
+public class TestableLooperJUnit4Test {
+ @Rule
+ public final TestWithLooperRule mTestWithLooperRule = new TestWithLooperRule();
+
+ @Test(expected = Exception.class)
+ public void testException() throws Exception {
+ throw new Exception("this exception is expected");
+ }
+}
+
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index a826646f69f3..1bcfaf60857d 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -93,13 +93,25 @@ public class TestLooper {
try {
mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
- ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
- .get(null);
+ ThreadLocal<Looper> threadLocalLooper =
+ (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null);
threadLocalLooper.set(mLooper);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
+ // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
+ try {
+ Field messageQueueUseConcurrentField =
+ MessageQueue.class.getDeclaredField("mUseConcurrent");
+ messageQueueUseConcurrentField.setAccessible(true);
+ messageQueueUseConcurrentField.set(mLooper.getQueue(), false);
+ } catch (NoSuchFieldException e) {
+ // Ignore - maybe this is not CombinedMessageQueue?
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Reflection error constructing or accessing looper", e);
+ }
+
mClock = clock;
}