summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/AppJankTest/Android.bp38
-rw-r--r--tests/AppJankTest/AndroidManifest.xml49
-rw-r--r--tests/AppJankTest/AndroidTest.xml38
-rw-r--r--tests/AppJankTest/OWNERS4
-rw-r--r--tests/AppJankTest/res/layout/jank_tracker_activity_layout.xml47
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java22
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java215
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java347
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java32
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java171
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankUtils.java52
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java227
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/TestWidget.java69
-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.kt48
-rw-r--r--tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java29
-rw-r--r--tests/BinaryTransparencyHostTest/Android.bp2
-rw-r--r--tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java9
-rw-r--r--tests/BootImageProfileTest/Android.bp1
-rw-r--r--tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml8
-rw-r--r--tests/CtsSurfaceControlTestsStaging/AndroidTest.xml3
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java260
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java43
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml32
-rw-r--r--tests/FlickerTests/ActivityEmbedding/Android.bp70
-rw-r--r--tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml6
-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/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/rtl/RTLStartSecondaryWithPlaceholderTest.kt2
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt2
-rw-r--r--tests/FlickerTests/Android.bp2
-rw-r--r--tests/FlickerTests/AppClose/AndroidTestTemplate.xml6
-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.bp58
-rw-r--r--tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml6
-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.kt2
-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.kt19
-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/FlickerService/AndroidTestTemplate.xml6
-rw-r--r--tests/FlickerTests/IME/Android.bp82
-rw-r--r--tests/FlickerTests/IME/AndroidTestTemplate.xml6
-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.kt2
-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.kt2
-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.kt2
-rw-r--r--tests/FlickerTests/Notification/AndroidTestTemplate.xml6
-rw-r--r--tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt29
-rw-r--r--tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml6
-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/AndroidTestTemplate.xml6
-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/test-apps/app-helpers/Android.bp1
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt41
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt151
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java306
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt10
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt39
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt334
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt44
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml41
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml18
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java21
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java71
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java31
-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/StartMediaProjectionActivity.java18
-rw-r--r--tests/Input/Android.bp1
-rw-r--r--tests/Input/res/xml/bookmarks.xml60
-rw-r--r--tests/Input/res/xml/bookmarks_legacy.xml54
-rw-r--r--tests/Input/res/xml/keyboard_glyph_maps.xml4
-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.kt24
-rw-r--r--tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt29
-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/InputDataStoreTests.kt504
-rw-r--r--tests/Input/src/com/android/server/input/InputGestureManagerTests.kt210
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt24
-rw-r--r--tests/Input/src/com/android/server/input/InputShellCommandTest.java15
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt1253
-rw-r--r--tests/Input/src/com/android/server/input/KeyRemapperTests.kt32
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt787
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt40
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt35
-rw-r--r--tests/Input/src/com/android/server/input/PointerIconCacheTest.kt135
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java71
-rw-r--r--tests/Input/src/com/android/test/input/MockInputManagerRule.kt42
-rw-r--r--tests/Internal/Android.bp27
-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/os/ApplicationSharedMemoryTest.java124
-rw-r--r--tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java56
-rw-r--r--tests/PackageWatchdog/Android.bp8
-rw-r--r--tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java297
-rw-r--r--tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java4
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java283
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java3
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java3
-rw-r--r--tests/TouchLatency/app/src/main/res/values/styles.xml2
-rw-r--r--tests/Tracing/TEST_MAPPING2
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java2
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java (renamed from tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java)104
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java23
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java42
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java6
-rw-r--r--tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java108
-rw-r--r--tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java32
-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.bp3
-rw-r--r--tests/broadcasts/unit/AndroidManifest.xml4
-rw-r--r--tests/broadcasts/unit/OWNERS2
-rw-r--r--tests/broadcasts/unit/TEST_MAPPING10
-rw-r--r--tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java334
-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/AndroidManifest.xml5
-rw-r--r--tests/graphics/SilkFX/res/layout/activity_background_blur.xml277
-rw-r--r--tests/graphics/SilkFX/res/layout/activity_glass.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/color_mode_controls.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/common_base.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/hdr_glows.xml3
-rw-r--r--tests/graphics/SilkFX/res/layout/view_blur_behind.xml148
-rw-r--r--tests/graphics/SilkFX/res/values/style.xml7
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt4
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt30
-rw-r--r--tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java32
-rw-r--r--tests/testables/Android.bp9
-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/tests/Android.bp7
-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/AnimatorTestRuleToolkitTest.kt201
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java42
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java4
178 files changed, 7533 insertions, 2537 deletions
diff --git a/tests/AppJankTest/Android.bp b/tests/AppJankTest/Android.bp
new file mode 100644
index 000000000000..c3cda6a41cbb
--- /dev/null
+++ b/tests/AppJankTest/Android.bp
@@ -0,0 +1,38 @@
+// 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",
+ "androidx.test.uiautomator_uiautomator",
+ ],
+ 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..861a79c6f0ed
--- /dev/null
+++ b/tests/AppJankTest/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?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 android:appCategory="news">
+ <activity android:name=".JankTrackerActivity"
+ android:exported="true"
+ android:label="JankTrackerActivity"
+ android:launchMode="singleTop">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".EmptyActivity"
+ android:exported="true"
+ android:label="EmptyActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
+ </intent-filter>
+ </activity>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <!-- self-instrumenting test package. -->
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Core tests of App Jank Tracking"
+ android:targetPackage="android.app.jank.tests">
+ </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/res/layout/jank_tracker_activity_layout.xml b/tests/AppJankTest/res/layout/jank_tracker_activity_layout.xml
new file mode 100644
index 000000000000..65def7f2d5b6
--- /dev/null
+++ b/tests/AppJankTest/res/layout/jank_tracker_activity_layout.xml
@@ -0,0 +1,47 @@
+<?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.
+ -->
+
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <LinearLayout
+ android:id="@+id/linear_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <EditText
+ android:id="@+id/edit_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textEnableTextConversionSuggestions"
+ android:text="Edit Text"/>
+ <TextView android:id="@+id/text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Text View"
+ />
+ <android.app.jank.tests.TestWidget
+ android:id="@+id/jank_tracker_widget"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+</ScrollView> \ 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/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
new file mode 100644
index 000000000000..34f0c191ecf5
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -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.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.jank.AppJankStats;
+import android.app.jank.Flags;
+import android.app.jank.JankDataProcessor;
+import android.app.jank.JankTracker;
+import android.app.jank.StateTracker;
+import android.content.Intent;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.widget.EditText;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This file contains tests that verify the proper functionality of the Jank Tracking feature.
+ * All tests should obtain references to necessary objects through View type interfaces, rather
+ * than direct instantiation. When operating outside of a testing environment, the expected
+ * behavior is to retrieve the necessary objects using View type interfaces. This approach ensures
+ * that calls are correctly routed down to the activity level. Any modifications to the call
+ * routing should result in test failures, which might happen with direct instantiations.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IntegrationTests {
+ public static final int WAIT_FOR_TIMEOUT_MS = 5000;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ private Activity mEmptyActivity;
+
+ public UiDevice mDevice;
+ private Instrumentation mInstrumentation;
+ private ActivityTestRule<JankTrackerActivity> mJankTrackerActivityRule =
+ new ActivityTestRule<>(
+ JankTrackerActivity.class,
+ false,
+ false);
+
+ private ActivityTestRule<EmptyActivity> mEmptyActivityRule =
+ new ActivityTestRule<>(EmptyActivity.class, false , true);
+
+ @Before
+ public void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mDevice = UiDevice.getInstance(mInstrumentation);
+ }
+
+
+ /**
+ * Get a JankTracker object from a view and confirm it's not null.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void getJankTacker_confirmNotNull() {
+ Activity jankTrackerActivity = mJankTrackerActivityRule.launchActivity(null);
+ EditText editText = jankTrackerActivity.findViewById(R.id.edit_text);
+
+ mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS);
+
+ JankTracker jankTracker = editText.getJankTracker();
+ assertTrue(jankTracker != null);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void reportJankStats_confirmPendingStatsIncreases() {
+ Activity jankTrackerActivity = mJankTrackerActivityRule.launchActivity(null);
+ EditText editText = jankTrackerActivity.findViewById(R.id.edit_text);
+ JankTracker jankTracker = editText.getJankTracker();
+
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ jankTracker.getPendingJankStats();
+ assertEquals(0, pendingStats.size());
+
+ editText.reportAppJankStats(JankUtils.getAppJankStats());
+
+ // reportAppJankStats performs the work on a background thread, check periodically to see
+ // if the work is complete.
+ for (int i = 0; i < 10; i++) {
+ try {
+ Thread.sleep(100);
+ if (jankTracker.getPendingJankStats().size() > 0) {
+ break;
+ }
+ } catch (InterruptedException exception) {
+ //do nothing and continue
+ }
+ }
+
+ pendingStats = jankTracker.getPendingJankStats();
+
+ assertEquals(1, pendingStats.size());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void simulateWidgetStateChanges_confirmStateChangesAreTracked() {
+ JankTrackerActivity jankTrackerActivity =
+ mJankTrackerActivityRule.launchActivity(null);
+ TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget);
+ JankTracker jankTracker = testWidget.getJankTracker();
+ jankTracker.forceListenerRegistration();
+
+ ArrayList<StateTracker.StateData> uiStates = new ArrayList<>();
+ // Get the current UI states, at this point only the activity name should be in the UI
+ // states list.
+ jankTracker.getAllUiStates(uiStates);
+
+ assertEquals(1, uiStates.size());
+
+ // This should add a UI state to be tracked.
+ testWidget.simulateAnimationStarting();
+ uiStates.clear();
+ jankTracker.getAllUiStates(uiStates);
+
+ assertEquals(2, uiStates.size());
+
+ // Stop the animation
+ testWidget.simulateAnimationEnding();
+ uiStates.clear();
+ jankTracker.getAllUiStates(uiStates);
+
+ assertEquals(2, uiStates.size());
+
+ // Confirm the Animation state has a VsyncIdEnd that is not default, indicating the end
+ // of that state.
+ for (int i = 0; i < uiStates.size(); i++) {
+ StateTracker.StateData stateData = uiStates.get(i);
+ if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) {
+ assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd);
+ }
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingPaused_whenActivityNoLongerVisible() {
+ JankTrackerActivity jankTrackerActivity =
+ mJankTrackerActivityRule.launchActivity(null);
+ TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget);
+ JankTracker jankTracker = testWidget.getJankTracker();
+ jankTracker.forceListenerRegistration();
+
+ assertTrue(jankTracker.shouldTrack());
+
+ // Send jankTrackerActivity to the background
+ mDevice.pressHome();
+ mDevice.waitForIdle(WAIT_FOR_TIMEOUT_MS);
+
+ assertFalse(jankTracker.shouldTrack());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingResumed_whenActivityBecomesVisibleAgain() {
+ mEmptyActivityRule.launchActivity(null);
+ mEmptyActivity = mEmptyActivityRule.getActivity();
+ JankTrackerActivity jankTrackerActivity =
+ mJankTrackerActivityRule.launchActivity(null);
+ TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget);
+ JankTracker jankTracker = testWidget.getJankTracker();
+ jankTracker.forceListenerRegistration();
+
+ // Send jankTrackerActivity to the background
+ mDevice.pressHome();
+ mDevice.waitForIdle(WAIT_FOR_TIMEOUT_MS);
+
+ assertFalse(jankTracker.shouldTrack());
+
+ Intent resumeJankTracker = new Intent(mInstrumentation.getContext(),
+ JankTrackerActivity.class);
+ resumeJankTracker.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ mEmptyActivity.startActivity(resumeJankTracker);
+ mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS);
+
+ assertTrue(jankTracker.shouldTrack());
+ }
+}
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..30c568be7716
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -0,0 +1,347 @@
+/*
+ * 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.AppJankStats;
+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.HashMap;
+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);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void mergeAppJankStats_confirmStatAddedToPendingStats() {
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ mJankDataProcessor.getPendingJankStats();
+
+ assertEquals(pendingStats.size(), 0);
+
+ AppJankStats jankStats = JankUtils.getAppJankStats();
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+
+ pendingStats = mJankDataProcessor.getPendingJankStats();
+
+ assertEquals(pendingStats.size(), 1);
+ }
+
+ /**
+ * This test confirms matching states are combined into one pending stat. When JankStats are
+ * merged from outside the platform they will contain widget category, widget id and widget
+ * state. If an incoming JankStats matches a pending stat on all those fields the incoming
+ * JankStat will be merged into the existing stat.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void mergeAppJankStats_confirmStatsWithMatchingStatesAreCombinedIntoOnePendingStat() {
+ AppJankStats jankStats = JankUtils.getAppJankStats();
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ mJankDataProcessor.getPendingJankStats();
+ assertEquals(pendingStats.size(), 1);
+
+ AppJankStats secondJankStat = JankUtils.getAppJankStats();
+ mJankDataProcessor.mergeJankStats(secondJankStat, sActivityName);
+
+ pendingStats = mJankDataProcessor.getPendingJankStats();
+
+ assertEquals(pendingStats.size(), 1);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void mergeAppJankStats_whenStatsWithMatchingStatesMerge_confirmFrameCountsAdded() {
+ AppJankStats jankStats = JankUtils.getAppJankStats();
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ mJankDataProcessor.getPendingJankStats();
+
+ String statKey = pendingStats.keySet().iterator().next();
+ JankDataProcessor.PendingJankStat pendingStat = pendingStats.get(statKey);
+
+ assertEquals(pendingStats.size(), 1);
+ // The same jankStats objects are merged twice, this should result in the frame counts being
+ // doubled.
+ assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames());
+ assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames());
+
+ int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters();
+ int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets();
+
+ for (int i = 0; i < frameOverrunBuckets.length; i++) {
+ assertEquals(originalHistogramBuckets[i] * 2, frameOverrunBuckets[i]);
+ }
+ }
+
+ // 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/JankTrackerActivity.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java
new file mode 100644
index 000000000000..80ab6ad3e587
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+import android.os.Bundle;
+
+
+public class JankTrackerActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.jank_tracker_activity_layout);
+ }
+}
+
+
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..1bdf019d6c42
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.assertNotNull;
+
+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 com.android.internal.policy.DecorView;
+
+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());
+ }
+
+ /**
+ * Test confirms a JankTracker object is retrieved from the activity.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_LOGGING_ENABLED)
+ public void jankTracker_NotNull_WhenRetrievedFromDecorView() {
+ DecorView decorView = (DecorView) sActivityDecorView;
+ JankTracker jankTracker = decorView.getJankTracker();
+
+ assertNotNull(jankTracker);
+ }
+}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
new file mode 100644
index 000000000000..0b4d97ed20d6
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.jank.AppJankStats;
+import android.app.jank.FrameOverrunHistogram;
+
+public class JankUtils {
+ private static final int APP_ID = 25;
+
+ /**
+ * Returns a mock AppJankStats object to be used in tests.
+ */
+ public static AppJankStats getAppJankStats() {
+ AppJankStats jankStats = new AppJankStats(
+ /*App Uid*/APP_ID,
+ /*Widget Id*/"test widget id",
+ /*Widget Category*/AppJankStats.SCROLL,
+ /*Widget State*/AppJankStats.SCROLLING,
+ /*Total Frames*/100,
+ /*Janky Frames*/25,
+ getOverrunHistogram()
+ );
+ return jankStats;
+ }
+
+ /**
+ * Returns a mock histogram to be used with an AppJankStats object.
+ */
+ public static FrameOverrunHistogram getOverrunHistogram() {
+ FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram();
+ overrunHistogram.addFrameOverrunMillis(-2);
+ overrunHistogram.addFrameOverrunMillis(1);
+ overrunHistogram.addFrameOverrunMillis(5);
+ overrunHistogram.addFrameOverrunMillis(25);
+ return overrunHistogram;
+ }
+}
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/AppJankTest/src/android/app/jank/tests/TestWidget.java b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
new file mode 100644
index 000000000000..5fff46038ead
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
@@ -0,0 +1,69 @@
+/*
+ * 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.jank.AppJankStats;
+import android.app.jank.JankTracker;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class TestWidget extends View {
+
+ private JankTracker mJankTracker;
+
+ /**
+ * Create JankTrackerView
+ */
+ public TestWidget(Context context) {
+ super(context);
+ }
+
+ /**
+ * Create JankTrackerView, needed by system when inflating views defined in a layout file.
+ */
+ public TestWidget(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ }
+
+ /**
+ * Mock starting an animation.
+ */
+ public void simulateAnimationStarting() {
+ if (jankTrackerCreated()) {
+ mJankTracker.addUiState(AppJankStats.ANIMATION,
+ Integer.toString(this.getId()), AppJankStats.ANIMATING);
+ }
+ }
+
+ /**
+ * Mock ending an animation.
+ */
+ public void simulateAnimationEnding() {
+ if (jankTrackerCreated()) {
+ mJankTracker.removeUiState(AppJankStats.ANIMATION,
+ Integer.toString(this.getId()), AppJankStats.ANIMATING);
+ }
+ }
+
+ private boolean jankTrackerCreated() {
+ if (mJankTracker == null) {
+ mJankTracker = getJankTracker();
+ }
+ return mJankTracker != null;
+ }
+}
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 4712d6b51bd5..4d1a1a55af74 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -3,11 +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
@@ -72,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
@@ -88,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
@@ -108,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
@@ -126,7 +127,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_SUCCESS)
+ assertThat(result).isEqualTo(0)
}
@Test
@@ -143,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
@@ -159,7 +160,7 @@ 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
@@ -176,7 +177,7 @@ class AttestationVerificationPeerDeviceVerifierTest {
TYPE_CHALLENGE, challengeRequirements,
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_SUCCESS)
+ assertThat(result).isEqualTo(0)
}
@Test
@@ -191,10 +192,28 @@ class AttestationVerificationPeerDeviceVerifierTest {
val result = verifier.verifyAttestation(
TYPE_CHALLENGE, challengeRequirements,
- // The patch date of this file is early 2022
TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
)
- assertThat(result).isEqualTo(RESULT_FAILURE)
+ 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
@@ -210,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
@@ -232,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() {
@@ -247,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> {
@@ -281,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 30cc002b4144..6d818d7287b0 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, false, false, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, false, false, false, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
@@ -168,32 +168,27 @@ public class BatteryUsageStatsPerfTest {
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .setConsumedPower(123)
- .setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 10100)
- .setConsumedPower(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200)
- .setUsageDurationMillis(
- BatteryConsumer.POWER_COMPONENT_CPU, 10300)
- .setUsageDurationMillis(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
+ .addConsumedPower(123)
+ .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100)
+ .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200)
+ .addUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CPU, 10300)
+ .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
for (int i = 0; i < 1000; i++) {
final UidBatteryConsumer.Builder consumerBuilder =
builder.getOrCreateUidBatteryConsumerBuilder(i)
.setPackageWithHighestDrain("example.packagename" + i)
- .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000)
- .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000);
+ .setTimeInProcessStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000)
+ .setTimeInProcessStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000);
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- consumerBuilder.setConsumedPower(componentId, componentId * 123.0,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE);
- consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000);
+ consumerBuilder.addConsumedPower(componentId, componentId * 123.0);
+ consumerBuilder.addUsageDurationMillis(componentId, componentId * 1000);
}
consumerBuilder
- .setConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
- .setUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
+ .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234)
+ .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321);
}
return builder.build();
}
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index e14e5fea001f..1c8386add1b1 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -31,6 +31,8 @@ java_test_host {
],
static_libs: [
"truth",
+ "flag-junit-host",
+ "android.app.flags-aconfig-java-host",
],
device_common_data: [
":BinaryTransparencyTestApp",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 6e5f08a11ed8..6d8dbcb5c963 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -24,6 +24,9 @@ import static org.junit.Assert.fail;
import android.platform.test.annotations.LargeTest;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.log.LogUtil.CLog;
@@ -34,6 +37,7 @@ import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,6 +53,10 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
/** Waiting time for the job to be scheduled */
private static final int JOB_CREATION_MAX_SECONDS = 30;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+
@Before
public void setUp() throws Exception {
cancelPendingJob();
@@ -123,6 +131,7 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
}
}
+ @RequiresFlagsDisabled(android.app.Flags.FLAG_BACKGROUND_INSTALL_CONTROL_CALLBACK_API)
@Test
public void testPreloadUpdateTriggersJobScheduling() throws Exception {
try {
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/CtsSurfaceControlTestsStaging/AndroidManifest.xml b/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml
index d8eb9ff37e78..da510fcd4d63 100644
--- a/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml
+++ b/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml
@@ -18,12 +18,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.view.surfacecontroltests">
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>
+ <uses-permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"/>
+
<application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner"/>
<activity
android:name=".GraphicsActivity"
android:exported="false">
</activity>
+ <activity android:name=".SurfaceControlPictureProfileTestActivity"
+ android:exported="true"
+ android:turnScreenOn="true"
+ android:showWhenLocked="true"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml b/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml
index 5c0163fcfa7e..025bf378d82f 100644
--- a/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml
+++ b/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml
@@ -21,6 +21,9 @@
<option name="install-arg" value="-t" />
<option name="test-file-name" value="CtsSurfaceControlTestsStaging.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" >
+ <option name="flag-value" value="media_tv/android.media.tv.flags.apply_picture_profiles=true" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.view.surfacecontroltests" />
<option name="hidden-api-checks" value="false" />
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
new file mode 100644
index 000000000000..135f7102b8a9
--- /dev/null
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
@@ -0,0 +1,260 @@
+/*
+ * 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.view.surfacecontroltests;
+
+import static android.Manifest.permission.OBSERVE_PICTURE_PROFILES;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.Arrays.stream;
+
+import android.hardware.HardwareBuffer;
+import android.media.quality.PictureProfileHandle;
+import android.os.Process;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlActivePicture;
+import android.view.SurfaceControlActivePictureListener;
+import android.view.SurfaceView;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.LongStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SurfaceControlPictureProfileTest {
+ private static final String TAG = SurfaceControlPictureProfileTest.class.getSimpleName();
+
+ private SurfaceControl[] mSurfaceControls;
+ private SurfaceControl mSurfaceControl;
+
+ @Rule
+ public ActivityTestRule<SurfaceControlPictureProfileTestActivity> mActivityRule =
+ new ActivityTestRule<>(SurfaceControlPictureProfileTestActivity.class);
+
+ @Before
+ public void setup() {
+ SurfaceView[] surfaceViews = mActivityRule.getActivity().getSurfaceViews();
+ mSurfaceControls = new SurfaceControl[surfaceViews.length];
+ // Create a child surface control so we can set a buffer, priority and profile handle all
+ // on one single surface control
+ for (int i = 0; i < mSurfaceControls.length; ++i) {
+ mSurfaceControls[i] = new SurfaceControl.Builder().setName("test").setHidden(false)
+ .setParent(surfaceViews[i].getSurfaceControl()).build();
+ }
+ mSurfaceControl = mSurfaceControls[0];
+ }
+
+ @Test
+ public void whenPictureProfileApplied_noExecptionsThrown() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ assumeTrue("Skipping test because no picture profile support",
+ SurfaceControl.getMaxPictureProfiles() > 0);
+
+ // TODO(b/337330263): Load the handle from MediaQualityManager instead
+ PictureProfileHandle handle = new PictureProfileHandle(1);
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction()
+ .setBuffer(mSurfaceControl, buffer)
+ .setPictureProfileHandle(mSurfaceControl, handle)
+ .apply();
+ }
+
+ @Test
+ public void whenStartsListening_callsListener() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ assumeTrue("Skipping test because no picture profile support",
+ SurfaceControl.getMaxPictureProfiles() > 0);
+
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>();
+ SurfaceControlActivePicture[] pictures;
+ SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() {
+ @Override
+ public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) {
+ picturesQueue.add(pictures);
+ }
+ };
+ // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead
+ adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES);
+ listener.startListening();
+ {
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply();
+ }
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(pictures).isEmpty();
+ }
+
+ @Test
+ public void whenPictureProfileApplied_callsListenerWithUidAndProfileId() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ assumeTrue("Skipping test because no picture profile support",
+ SurfaceControl.getMaxPictureProfiles() > 0);
+
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>();
+ SurfaceControlActivePicture[] pictures;
+ SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() {
+ @Override
+ public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) {
+ picturesQueue.add(pictures);
+ }
+ };
+ // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead
+ adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES);
+ listener.startListening();
+ {
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply();
+ }
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(pictures).isEmpty();
+
+ // TODO(b/337330263): Load the handle from MediaQualityManager instead
+ PictureProfileHandle handle = new PictureProfileHandle(1);
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction()
+ .setBuffer(mSurfaceControl, buffer)
+ .setPictureProfileHandle(mSurfaceControl, handle)
+ .apply();
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
+ .containsExactly(handle.getId());
+ assertThat(stream(pictures).map(picture -> picture.getOwnerUid()))
+ .containsExactly(Process.myUid());
+ }
+
+ @Test
+ public void whenPriorityChanges_callsListenerOnlyForLowerPriorityLayers() {
+ assumeTrue("Skipping test because feature flag is disabled",
+ com.android.graphics.libgui.flags.Flags.applyPictureProfiles());
+ // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead
+ int maxPictureProfiles = SurfaceControl.getMaxPictureProfiles();
+ assumeTrue("Skipping test because no picture profile support", maxPictureProfiles > 0);
+
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>();
+ SurfaceControlActivePicture[] pictures;
+ SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() {
+ @Override
+ public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) {
+ picturesQueue.add(pictures);
+ }
+ };
+ // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead
+ adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES);
+ listener.startListening();
+ {
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply();
+ }
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(pictures).isEmpty();
+
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ // Use one more picture profile than allowed
+ for (int i = 0; i <= maxPictureProfiles; ++i) {
+ // Increase the number of surface views as necessary to support device configuration.
+ assertThat(i).isLessThan(mSurfaceControls.length);
+
+ // TODO(b/337330263): Load the handle from MediaQualityManager instead
+ PictureProfileHandle handle = new PictureProfileHandle(i + 1);
+ HardwareBuffer buffer = getSolidBuffer(100, 100);
+ transaction
+ .setBuffer(mSurfaceControls[i], buffer)
+ .setPictureProfileHandle(mSurfaceControls[i], handle)
+ .setContentPriority(mSurfaceControls[i], 0);
+ }
+ // Make the first layer low priority (high value)
+ transaction.setContentPriority(mSurfaceControls[0], 2);
+ // Make the last layer higher priority (lower value)
+ transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 1);
+ transaction.apply();
+
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ assertThat(stream(pictures).map(picture -> picture.getLayerId()))
+ .containsNoDuplicates();
+ // Expect all but the first layer to be listed as an active picture
+ assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
+ .containsExactlyElementsIn(toIterableRange(2, maxPictureProfiles + 1));
+
+ // Change priority and ensure that the first layer gets access
+ new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 0).apply();
+ pictures = pollMs(picturesQueue, 200);
+ assertThat(pictures).isNotNull();
+ // Expect all but the last layer to be listed as an active picture
+ assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
+ .containsExactlyElementsIn(toIterableRange(1, maxPictureProfiles));
+ }
+
+ private static SurfaceControlActivePicture[] pollMs(
+ BlockingQueue<SurfaceControlActivePicture[]> picturesQueue, int waitMs) {
+ SurfaceControlActivePicture[] pictures = null;
+ long nowMs = System.currentTimeMillis();
+ long endTimeMs = nowMs + waitMs;
+ while (nowMs < endTimeMs && pictures == null) {
+ try {
+ pictures = picturesQueue.poll(endTimeMs - nowMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // continue polling until timeout when interrupted
+ }
+ nowMs = System.currentTimeMillis();
+ }
+ return pictures;
+ }
+
+ Iterable<Long> toIterableRange(int start, int stop) {
+ return () -> LongStream.rangeClosed(start, stop).iterator();
+ }
+
+ private void adoptShellPermissionIdentity(String permission) {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(permission);
+ }
+
+ private HardwareBuffer getSolidBuffer(int width, int height) {
+ // We can assume that RGBA_8888 format is supported for every platform.
+ return HardwareBuffer.create(
+ width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_CPU_WRITE_OFTEN);
+ }
+}
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java
new file mode 100644
index 000000000000..42fcb261fa9d
--- /dev/null
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java
@@ -0,0 +1,43 @@
+/*
+ * 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.view.surfacecontroltests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.SurfaceView;
+
+public class SurfaceControlPictureProfileTestActivity extends Activity {
+ private SurfaceView[] mSurfaceViews;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.picture_profile_test_layout);
+ mSurfaceViews = new SurfaceView[3];
+ mSurfaceViews[0] = (SurfaceView) findViewById(R.id.surfaceview1);
+ mSurfaceViews[1] = (SurfaceView) findViewById(R.id.surfaceview2);
+ mSurfaceViews[2] = (SurfaceView) findViewById(R.id.surfaceview3);
+ }
+
+ public SurfaceView getSurfaceView() {
+ return mSurfaceViews[0];
+ }
+
+ public SurfaceView[] getSurfaceViews() {
+ return mSurfaceViews;
+ }
+}
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml b/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml
new file mode 100644
index 000000000000..9aa25785d9f2
--- /dev/null
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml
@@ -0,0 +1,32 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <SurfaceView android:id="@+id/surfaceview1"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+ <SurfaceView android:id="@+id/surfaceview2"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+ <SurfaceView android:id="@+id/surfaceview3"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+</LinearLayout>
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index c681ce96a269..529f84ac4e90 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -24,71 +24,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-filegroup {
- name: "FlickerTestsOtherCommon-src",
- srcs: ["src/**/ActivityEmbeddingTestBase.kt"],
-}
-
-filegroup {
- name: "FlickerTestsOtherOpen-src",
- srcs: ["src/**/open/*"],
-}
-
-filegroup {
- name: "FlickerTestsOtherRotation-src",
- srcs: ["src/**/rotation/*"],
-}
-
-java_library {
- name: "FlickerTestsOtherCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsOtherCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
-
-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/*"],
-}
-
-android_test {
- name: "FlickerTestsOtherOpen",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: [":FlickerTestsOtherOpen-src"],
-}
-
-android_test {
- name: "FlickerTestsOtherRotation",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: [":FlickerTestsOtherRotation-src"],
-}
-
-android_test {
- name: "FlickerTestsOther",
- defaults: ["FlickerTestsOtherDefaults"],
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsOtherOpen-src",
- ":FlickerTestsOtherRotation-src",
- ":FlickerTestsOtherCommon-src",
- ],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
android_test {
name: "FlickerTestsActivityEmbedding",
defaults: ["FlickerTestsDefault"],
@@ -97,10 +32,7 @@ android_test {
instrumentation_target_package: "com.android.server.wm.flicker",
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsOtherCommon",
- ],
+ static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 82de070921f0..685ae9a5fef2 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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/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/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 c3e1a1fa2e18..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
@@ -47,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)
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 27e9ffa4cea5..f44eacbaafbf 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -41,13 +41,13 @@ java_defaults {
"platform-test-annotations",
"wm-flicker-common-app-helpers",
"wm-shell-flicker-utils",
+ "systemui-tapl",
],
data: [":FlickerTestApp"],
}
java_library {
name: "wm-flicker-common-assertions",
- platform_apis: true,
optimize: {
enabled: false,
},
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4ffb11ab92ae..5f92d7fe830b 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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 b61739f100ab..17d0f967b1bd 100644
--- a/tests/FlickerTests/AppLaunch/Android.bp
+++ b/tests/FlickerTests/AppLaunch/Android.bp
@@ -24,69 +24,13 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-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",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [":FlickerTestsAppLaunch1-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
- ],
- 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",
- ],
- data: ["trace_config/*"],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
android_test {
name: "FlickerTestsAppLaunch",
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsAppLaunchCommon",
- ],
+ static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
}
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 0fa4d07b2eca..1b90e99a8ba2 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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..e59b6bd0617c 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:ActivityTransitionTest`
*
* 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..2bf8cc40d0a0 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:OpenAppFromIconColdTest`
*
* 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..9c6bf9de37ce 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:OpenAppFromIntentColdAfterCameraTest`
*
* 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..1a53a611c8d7 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:OpenAppFromIntentColdTest`
*
* 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..14b6a18bfe2e 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:OpenAppFromIntentWarmTest`
*
* 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 6e6a3275191f..f30fe96b9d05 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:OpenAppFromLockscreenViaIntentTest`
*
* Actions:
* ```
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..9c552eb478d4 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:OpenCameraFromHomeOnDoubleClickPowerButtonTest`
*
* Actions:
* ```
@@ -140,14 +140,8 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest)
@Postsubmit
@Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- flicker.assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry(
- LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
- listOf(CAMERA_BACKGROUND)
- )
- }
- }
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
@Postsubmit
@Test
@@ -170,12 +164,5 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest)
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
-
- private val CAMERA_BACKGROUND =
- ComponentNameMatcher(
- "Background for SurfaceView" +
- "[com.google.android.GoogleCamera/" +
- "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]"
- )
}
}
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/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4d9fefbc7d88..ffdbb02984a7 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index f80e6b4b2f5e..cba3d09ebefd 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -24,27 +24,6 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-filegroup {
- name: "FlickerTestsImeCommon-src",
- srcs: ["src/**/common/*"],
-}
-
-filegroup {
- name: "FlickerTestsIme1-src",
- srcs: ["src/**/Close*"],
-}
-
-filegroup {
- name: "FlickerTestsIme2-src",
- srcs: ["src/**/ShowImeOnAppStart*"],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
android_test {
name: "FlickerTestsIme",
defaults: ["FlickerTestsDefault"],
@@ -60,67 +39,6 @@ android_test {
}
////////////////////////////////////////////////////////////////////////////////
-// Begin to cleanup after CL merges
-
-java_library {
- name: "FlickerTestsImeCommon",
- defaults: ["FlickerTestsDefault"],
- srcs: [":FlickerTestsImeCommon-src"],
- static_libs: ["FlickerTestsBase"],
-}
-
-android_test {
- name: "FlickerTestsIme1",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- test_suites: [
- "device-tests",
- "device-platinum-tests",
- ],
- srcs: [":FlickerTestsIme1-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "FlickerTestsIme2",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [":FlickerTestsIme2-src"],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "FlickerTestsIme3",
- defaults: ["FlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*"],
- exclude_srcs: [
- ":FlickerTestsIme1-src",
- ":FlickerTestsIme2-src",
- ":FlickerTestsImeCommon-src",
- ],
- static_libs: [
- "FlickerTestsBase",
- "FlickerTestsImeCommon",
- ],
- data: ["trace_config/*"],
-}
-
-// End to cleanup after CL merges
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
// Begin breakdowns for FlickerTestsIme module
test_module_config {
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b879c54dcab3..12670cda74b2 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 -->
@@ -43,6 +47,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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 522c68bba0d1..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)
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 82e53c81daaa..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)
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 eb63e4985a9f..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
@@ -39,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)
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 04b312a896b9..e2ac5a9579ae 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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 ad70757a9a4d..da90c4f624d2 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
@@ -16,6 +16,8 @@
package com.android.server.wm.flicker.notification
+import android.platform.systemui_tapl.controller.NotificationIdentity
+import android.platform.systemui_tapl.ui.Root
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.rule.DisableNotificationCooldownSettingRule
@@ -28,8 +30,6 @@ import android.tools.helpers.wakeUpAndGoToHomeScreen
import android.tools.traces.component.ComponentNameMatcher
import android.view.WindowInsets
import android.view.WindowManager
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
@@ -87,8 +87,9 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
.withWindowSurfaceDisappeared(ComponentNameMatcher.NOTIFICATION_SHADE)
.waitForAndVerify()
}
+
protected fun FlickerTestData.openAppFromNotification() {
- doOpenAppAndWait(startY = 10, endY = 3 * device.displayHeight / 4, steps = 25)
+ doOpenAppAndWait()
}
protected fun FlickerTestData.openAppFromLockNotification() {
@@ -101,25 +102,27 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
)
- doOpenAppAndWait(startY = insets.top + 100, endY = device.displayHeight / 2, steps = 4)
+ doOpenAppAndWait()
}
- protected fun FlickerTestData.doOpenAppAndWait(startY: Int, endY: Int, steps: Int) {
- // Swipe down to show the notification shade
- val x = device.displayWidth / 2
- device.swipe(x, startY, x, endY, steps)
- device.waitForIdle(2000)
- instrumentation.uiAutomation.syncInputTransactions()
+ protected fun FlickerTestData.doOpenAppAndWait() {
+ val shade = Root.get().openNotificationShade()
// Launch the activity by clicking the notification
+ // Post notification and ensure that it's collapsed
val notification =
- device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
- notification?.click() ?: error("Notification not found")
- instrumentation.uiAutomation.syncInputTransactions()
+ shade.notificationStack.findNotification(
+ NotificationIdentity(
+ type = NotificationIdentity.Type.BY_TEXT,
+ text = "Flicker Test Notification",
+ )
+ )
+ notification.clickToApp()
// Wait for the app to launch
wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
}
+
@Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
@Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 8acdabc2337d..1a4feb6e9eca 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 91ece214aad5..481a8bb66fee 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 -->
@@ -41,6 +45,8 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
<option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+ <!-- Disable AOD -->
+ <option name="run-command" value="settings put secure doze_always_on 0"/>
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
<option name="run-command" value="settings put system show_touches 1"/>
<option name="run-command" value="settings put system pointer_location 1"/>
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/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/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
new file mode 100644
index 000000000000..6573c2c83f20
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.content.Intent
+import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.testapp.ActivityOptions
+
+class BottomHalfPipAppHelper(
+ instrumentation: Instrumentation,
+ private val useLaunchingActivity: Boolean = false,
+) : PipAppHelper(
+ instrumentation,
+ appName = ActivityOptions.BottomHalfPip.LABEL,
+ componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT
+ .toFlickerComponent()
+) {
+ override val openAppIntent: Intent
+ get() = super.openAppIntent.apply {
+ component = if (useLaunchingActivity) {
+ ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT
+ } else {
+ ActivityOptions.BottomHalfPip.COMPONENT
+ }
+ }
+} \ No newline at end of file
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 c77413b6a55a..c1c5dc66bac1 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,18 +16,21 @@
package com.android.server.wm.flicker.helpers
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
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.platform.uiautomatorhelpers.DeviceHelpers
+import android.tools.PlatformConsts
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 android.window.DesktopModeFlags
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
@@ -35,8 +38,8 @@ 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
+import kotlin.math.abs
/**
* Wrapper class around App helper classes. This class adds functionality to the apps that the
@@ -59,6 +62,11 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
BOTTOM
}
+ enum class AppProperty {
+ STANDARD,
+ NON_RESIZABLE
+ }
+
/** Wait for an app moved to desktop to finish its transition. */
private fun waitForAppToMoveToDesktop(wmHelper: WindowManagerStateHelper) {
wmHelper
@@ -69,13 +77,28 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
.waitForAndVerify()
}
+ /** Launch an app and ensure it's moved to Desktop if it has not. */
+ fun enterDesktopMode(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH),
+ ) {
+ innerHelper.launchViaIntent(wmHelper)
+ if (!isInDesktopWindowingMode(wmHelper)) {
+ enterDesktopModeWithDrag(
+ wmHelper = wmHelper,
+ device = device,
+ motionEventHelper = motionEventHelper
+ )
+ }
+ }
+
/** Move an app to Desktop by dragging the app handle at the top. */
- fun enterDesktopWithDrag(
+ private fun enterDesktopModeWithDrag(
wmHelper: WindowManagerStateHelper,
device: UiDevice,
motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH)
) {
- innerHelper.launchViaIntent(wmHelper)
dragToDesktop(
wmHelper = wmHelper,
device = device,
@@ -102,13 +125,9 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
// drag the window to move to desktop
if (motionEventHelper.inputMethod == TOUCH
- && Flags.enableHoldToDragAppHandle()) {
+ && DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue) {
// Touch requires hold-to-drag.
- val downTime = SystemClock.uptimeMillis()
- motionEventHelper.actionDown(startX, startY, time = downTime)
- SystemClock.sleep(100L) // hold for 100ns before starting the move.
- motionEventHelper.actionMove(startX, startY, startX, endY, 100, downTime = downTime)
- motionEventHelper.actionUp(startX, endY, downTime = downTime)
+ motionEventHelper.holdToDrag(startX, startY, startX, endY, steps = 100)
} else {
device.drag(startX, startY, startX, endY, 100)
}
@@ -131,6 +150,48 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
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, isPip: Boolean = false) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val minimizeButton = getMinimizeButtonForTheApp(caption)
+ minimizeButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .apply {
+ if (isPip) withPipShown()
+ else
+ withWindowSurfaceDisappeared(innerHelper)
+ .withActivityState(innerHelper, PlatformConsts.STATE_STOPPED)
+ }
+ .waitForAndVerify()
+ }
+
+ private fun getHeaderEmptyView(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(HEADER_EMPTY_VIEW) }
+ ?: error("Unable to find resource $HEADER_EMPTY_VIEW\n")
+ }
+
+ /** Click on an existing window's header to bring it to the front. */
+ fun bringToFront(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val openHeaderView = getHeaderEmptyView(caption)
+ openHeaderView.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withTopVisibleApp(innerHelper)
+ .waitForAndVerify()
+ }
+
/** Open maximize menu and click snap resize button on the app header for the given app. */
fun snapResizeDesktopApp(
wmHelper: WindowManagerStateHelper,
@@ -140,7 +201,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
) {
val caption = getCaptionForTheApp(wmHelper, device)
val maximizeButton = getMaximizeButtonForTheApp(caption)
- maximizeButton?.longClick()
+ maximizeButton.longClick()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
@@ -162,11 +223,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
val expectedRect = Rect(displayRect).apply {
if (toLeft) right -= expectedWidth else left += expectedWidth
}
-
- wmHelper
- .StateSyncBuilder()
+ wmHelper.StateSyncBuilder()
.withAppTransitionIdle()
- .withSurfaceVisibleRegion(this, Region(expectedRect))
+ .withSurfaceMatchingVisibleRegion(
+ this,
+ Region(expectedRect),
+ { surfaceRegion, expectedRegion ->
+ areSnapWindowRegionsMatchingWithinThreshold(
+ surfaceRegion, expectedRegion, toLeft
+ )
+ })
.waitForAndVerify()
}
@@ -280,7 +346,11 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
val displayRect = getDisplayRect(wmHelper)
- val endX = if (isLeft) displayRect.left else displayRect.right
+ 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
@@ -324,6 +394,14 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
waitForTransitionToFullscreen(wmHelper)
}
+ /** Maximize an app by dragging the app handle to the top drag zone. */
+ fun maximizeAppWithDragToTopDragZone(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ ) {
+ dragAppWindowToTopDragZone(wmHelper, device)
+ }
+
private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
val displayRect = getDisplayRect(wmHelper)
@@ -384,8 +462,40 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
return metricInsets.getInsetsIgnoringVisibility(typeMask)
}
+ // Requirement of DesktopWindowingMode is having a minimum of 1 app in WINDOWING_MODE_FREEFORM.
+ private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) =
+ wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM
+
+ private fun areSnapWindowRegionsMatchingWithinThreshold(
+ surfaceRegion: Region, expectedRegion: Region, toLeft: Boolean
+ ): Boolean {
+ val surfaceBounds = surfaceRegion.bounds
+ val expectedBounds = expectedRegion.bounds
+ // If snapped to left, right bounds will be cut off by the center divider.
+ // Else if snapped to right, the left bounds will be cut off.
+ val leftSideMatching: Boolean
+ val rightSideMatching: Boolean
+ if (toLeft) {
+ leftSideMatching = surfaceBounds.left == expectedBounds.left
+ rightSideMatching =
+ abs(surfaceBounds.right - expectedBounds.right) <=
+ surfaceBounds.right * SNAP_WINDOW_MAX_THRESHOLD_DIFF
+ } else {
+ leftSideMatching =
+ abs(surfaceBounds.left - expectedBounds.left) <=
+ surfaceBounds.left * SNAP_WINDOW_MAX_THRESHOLD_DIFF
+ rightSideMatching = surfaceBounds.right == expectedBounds.right
+ }
+
+ return surfaceBounds.top == expectedBounds.top &&
+ surfaceBounds.bottom == expectedBounds.bottom &&
+ leftSideMatching &&
+ rightSideMatching
+ }
+
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"
@@ -394,7 +504,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
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"
+ const val HEADER_EMPTY_VIEW: String = "caption_handle"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
+ // In DesktopMode, window snap can be done with just a single window. In this case, the
+ // divider tiling between left and right window won't be shown, and hence its states are not
+ // obtainable in test.
+ // As the test should just focus on ensuring window goes to one side of the screen, an
+ // acceptable approach is to ensure snapped window still fills > 95% of either side of the
+ // screen.
+ const val SNAP_WINDOW_MAX_THRESHOLD_DIFF = 0.05
}
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
deleted file mode 100644
index eeee7b4dfc6b..000000000000
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.server.wm.flicker.helpers;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import androidx.annotation.Nullable;
-
-/**
- * Injects gestures given an {@link Instrumentation} object.
- */
-public class GestureHelper {
- // Inserted after each motion event injection.
- private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
-
- private final UiAutomation mUiAutomation;
-
- /**
- * Primary pointer should be cached here for separate release
- */
- @Nullable private PointerProperties mPrimaryPtrProp;
- @Nullable private PointerCoords mPrimaryPtrCoord;
- private long mPrimaryPtrDownTime;
-
- /**
- * A pair of floating point values.
- */
- public static class Tuple {
- public float x;
- public float y;
-
- public Tuple(float x, float y) {
- this.x = x;
- this.y = y;
- }
- }
-
- public GestureHelper(Instrumentation instrumentation) {
- mUiAutomation = instrumentation.getUiAutomation();
- }
-
- /**
- * Injects a series of {@link MotionEvent}s to simulate tapping.
- *
- * @param point coordinates of pointer to tap
- * @param times the number of times to tap
- */
- public boolean tap(@NonNull Tuple point, int times) throws InterruptedException {
- PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
- PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1);
-
- for (int i = 0; i <= times; i++) {
- // If already tapped, inject delay in between movements
- if (times > 0) {
- SystemClock.sleep(50L);
- }
- if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
- return false;
- }
- // Delay before releasing tap
- SystemClock.sleep(100L);
- if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
- *
- * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
- * will be cached for potential release later on by {@code releasePrimaryPointer()}
- *
- * @param startPoint initial coordinates of the primary pointer
- * @param endPoint final coordinates of the primary pointer
- * @param steps number of steps to take to animate dragging
- * @return true if gesture is injected successfully
- */
- public boolean dragWithoutRelease(@NonNull Tuple startPoint,
- @NonNull Tuple endPoint, int steps) {
- PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
- PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1);
-
- PointerProperties[] ptrProps = new PointerProperties[] { ptrProp };
- PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord };
-
- long downTime = SystemClock.uptimeMillis();
-
- if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) {
- return false;
- }
-
- // cache the primary pointer info for later potential release
- mPrimaryPtrProp = ptrProp;
- mPrimaryPtrCoord = ptrCoord;
- mPrimaryPtrDownTime = downTime;
-
- return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps);
- }
-
- /**
- * Release primary pointer if previous gesture has cached the primary pointer info.
- *
- * @return true if the release was injected successfully
- */
- public boolean releasePrimaryPointer() {
- if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) {
- return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime);
- }
-
- return false;
- }
-
- /**
- * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
- *
- * @param startPoint1 initial coordinates of the first pointer
- * @param startPoint2 initial coordinates of the second pointer
- * @param endPoint1 final coordinates of the first pointer
- * @param endPoint2 final coordinates of the second pointer
- * @param steps number of steps to take to animate pinching
- * @return true if gesture is injected successfully
- */
- public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2,
- @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) {
- PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
- PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER);
-
- PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1);
- PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1);
-
- PointerProperties[] ptrProps = new PointerProperties[] {
- ptrProp1, ptrProp2
- };
-
- PointerCoords[] ptrCoords = new PointerCoords[] {
- ptrCoord1, ptrCoord2
- };
-
- long downTime = SystemClock.uptimeMillis();
-
- if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) {
- return false;
- }
-
- if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) {
- return false;
- }
-
- if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 },
- downTime, steps)) {
- return false;
- }
-
- if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) {
- return false;
- }
-
- return primaryPointerUp(ptrProp1, ptrCoord1, downTime);
- }
-
- private boolean primaryPointerDown(@NonNull PointerProperties prop,
- @NonNull PointerCoords coord, long downTime) {
- MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1,
- new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
- return injectEventSync(event);
- }
-
- private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props,
- @NonNull PointerCoords[] coords, long downTime, int index) {
- // at least 2 pointers are needed
- if (props.length != coords.length || coords.length < 2) {
- return false;
- }
-
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN
- + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
- return injectEventSync(event);
- }
-
- private boolean movePointers(@NonNull PointerProperties[] props,
- @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) {
- // the number of endpoints should be the same as the number of pointers
- if (props.length != coords.length || coords.length != endPoints.length) {
- return false;
- }
-
- // prevent division by 0 and negative number of steps
- if (steps < 1) {
- steps = 1;
- }
-
- // save the starting points before updating any pointers
- Tuple[] startPoints = new Tuple[coords.length];
-
- for (int i = 0; i < coords.length; i++) {
- startPoints[i] = new Tuple(coords[i].x, coords[i].y);
- }
-
- MotionEvent event;
- long eventTime;
-
- for (int i = 0; i < steps; i++) {
- // inject a delay between movements
- SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
-
- // update the coordinates
- for (int j = 0; j < coords.length; j++) {
- coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
- coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
- }
-
- eventTime = SystemClock.uptimeMillis();
-
- event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE,
- coords.length, props, coords);
-
- boolean didInject = injectEventSync(event);
-
- if (!didInject) {
- return false;
- }
- }
-
- return true;
- }
-
- private boolean primaryPointerUp(@NonNull PointerProperties prop,
- @NonNull PointerCoords coord, long downTime) {
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1,
- new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
-
- return injectEventSync(event);
- }
-
- private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props,
- @NonNull PointerCoords[] coords, long downTime, int index) {
- // at least 2 pointers are needed
- if (props.length != coords.length || coords.length < 2) {
- return false;
- }
-
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP
- + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
-
- return injectEventSync(event);
- }
-
- private PointerCoords getPointerCoord(float x, float y, float pressure, float size) {
- PointerCoords ptrCoord = new PointerCoords();
- ptrCoord.x = x;
- ptrCoord.y = y;
- ptrCoord.pressure = pressure;
- ptrCoord.size = size;
- return ptrCoord;
- }
-
- private PointerProperties getPointerProp(int id, int toolType) {
- PointerProperties ptrProp = new PointerProperties();
- ptrProp.id = id;
- ptrProp.toolType = toolType;
- return ptrProp;
- }
-
- private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
- int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) {
- return MotionEvent.obtain(downTime, eventTime, action, pointerCount,
- ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f,
- 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
- }
-
- private boolean injectEventSync(InputEvent event) {
- return mUiAutomation.injectInputEvent(event, true);
- }
-}
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..d5334cbd541c 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
@@ -21,6 +21,7 @@ import android.graphics.Rect
import android.graphics.Region
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.helpers.FIND_TIMEOUT
+import android.tools.helpers.GestureHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.parsers.WindowManagerStateHelper
@@ -33,12 +34,13 @@ 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)
+ private val gestureHelper: GestureHelper =
+ GestureHelper(instrumentation)
fun clickRestart(wmHelper: WindowManagerStateHelper) {
val restartButton =
@@ -128,6 +130,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
index 86a0b0f8c66e..1fe60888fa52 100644
--- 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
@@ -54,7 +54,15 @@ class MotionEventHelper(
injectMotionEvent(ACTION_UP, x, y, downTime = downTime)
}
- fun actionMove(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int, downTime: Long) {
+ 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)
@@ -65,9 +73,33 @@ class MotionEventHelper(
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,
@@ -120,4 +152,9 @@ class MotionEventHelper(
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 931e4f88aa8d..de17bf422c0c 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
@@ -18,29 +18,26 @@ package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.content.Intent
-import android.graphics.Rect
import android.graphics.Region
import android.media.session.MediaController
import android.media.session.MediaSessionManager
-import android.tools.datatypes.coversMoreThan
-import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.apphelpers.BasePipAppHelper
import android.tools.helpers.FIND_TIMEOUT
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.ConditionsFactory
+import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
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.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
-open class PipAppHelper(instrumentation: Instrumentation) :
- StandardAppHelper(
- instrumentation,
- ActivityOptions.Pip.LABEL,
- ActivityOptions.Pip.COMPONENT.toFlickerComponent()
- ) {
+open class PipAppHelper(
+ instrumentation: Instrumentation,
+ appName: String = ActivityOptions.Pip.LABEL,
+ componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(),
+) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) {
private val mediaSessionManager: MediaSessionManager
get() =
context.getSystemService(MediaSessionManager::class.java)
@@ -52,189 +49,6 @@ open class PipAppHelper(instrumentation: Instrumentation) :
it.packageName == packageName
}
- private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
-
- open fun clickObject(resId: String) {
- val selector = By.res(packageName, resId)
- val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
-
- obj.click()
- }
-
- /** Drags the PIP window to the provided final coordinates without releasing the pointer. */
- fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
- val initWindowRect = Rect(getWindowRect(wmHelper))
-
- // initial pointer at the center of the window
- val initialCoord =
- GestureHelper.Tuple(
- initWindowRect.centerX().toFloat(),
- initWindowRect.centerY().toFloat()
- )
-
- // the offset to the right (or left) of the window center to drag the window to
- val offset = 50
-
- // the actual final x coordinate with the offset included;
- // if the pip window is closer to the right edge of the display the offset is negative
- // otherwise the offset is positive
- val endX =
- initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
- val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
-
- // drag to the final coordinate
- gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps)
- }
-
- /**
- * Releases the primary pointer.
- *
- * Injects the release of the primary pointer if the primary pointer info was cached after
- * another gesture was injected without pointer release.
- */
- fun releasePipAfterDragging() {
- gestureHelper.releasePrimaryPointer()
- }
-
- /**
- * Drags the PIP window away from the screen edge while not crossing the display center.
- *
- * @throws IllegalStateException if default display bounds are not available
- */
- fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) {
- val initWindowRect = Rect(getWindowRect(wmHelper))
-
- // initial pointer at the center of the window
- val startX = initWindowRect.centerX()
- val y = initWindowRect.centerY()
-
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
-
- // the offset to the right (or left) of the display center to drag the window to
- val offset = 20
-
- // the actual final x coordinate with the offset included;
- // if the pip window is closer to the right edge of the display the offset is positive
- // otherwise the offset is negative
- val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1)
-
- // drag the window to the left but not beyond the center of the display
- uiDevice.drag(startX, y, endX, y, steps)
- }
-
- /**
- * Returns true if PIP window is closer to the right edge of the display than left.
- *
- * @throws IllegalStateException if default display bounds are not available
- */
- fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean {
- val windowRect = getWindowRect(wmHelper)
-
- val displayRect =
- wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
- ?: throw IllegalStateException("Default display is null")
-
- return windowRect.centerX() > displayRect.centerX()
- }
-
- /**
- * Expands the PIP window by using the pinch out gesture.
- *
- * @param percent The percentage by which to increase the pip window size.
- * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
- */
- fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
- // the percentage must be between 0.0f and 1.0f
- if (percent <= 0.0f || percent > 1.0f) {
- throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
- }
-
- val windowRect = getWindowRect(wmHelper)
-
- // first pointer's initial x coordinate is halfway between the left edge and the center
- val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
- // second pointer's initial x coordinate is halfway between the right edge and the center
- val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
- // horizontal distance the window should increase by
- val distIncrease = windowRect.width() * percent
-
- // final x-coordinates
- val finalLeftX = initLeftX - (distIncrease / 2)
- val finalRightX = initRightX + (distIncrease / 2)
-
- // y-coordinate is the same throughout this animation
- val yCoord = windowRect.centerY().toFloat()
-
- var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
- // if distance per step is at least 1, then we can use the number of steps requested
- if (distIncrease.toInt() / (steps * 2) >= 1) {
- adjustedSteps = steps
- }
-
- // if the distance per step is less than 1, carry out the animation in two steps
- gestureHelper.pinch(
- GestureHelper.Tuple(initLeftX, yCoord),
- GestureHelper.Tuple(initRightX, yCoord),
- GestureHelper.Tuple(finalLeftX, yCoord),
- GestureHelper.Tuple(finalRightX, yCoord),
- adjustedSteps
- )
-
- waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
- }
-
- /**
- * Minimizes the PIP window by using the pinch in gesture.
- *
- * @param percent The percentage by which to decrease the pip window size.
- * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
- */
- fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
- // the percentage must be between 0.0f and 1.0f
- if (percent <= 0.0f || percent > 1.0f) {
- throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
- }
-
- val windowRect = getWindowRect(wmHelper)
-
- // first pointer's initial x coordinate is halfway between the left edge and the center
- val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat()
- // second pointer's initial x coordinate is halfway between the right edge and the center
- val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat()
-
- // decrease by the distance specified through the percentage
- val distDecrease = windowRect.width() * percent
-
- // get the final x-coordinates and make sure they are not passing the center of the window
- val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
- val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
-
- // y-coordinate is the same throughout this animation
- val yCoord = windowRect.centerY().toFloat()
-
- var adjustedSteps = MIN_STEPS_TO_ANIMATE
-
- // if distance per step is at least 1, then we can use the number of steps requested
- if (distDecrease.toInt() / (steps * 2) >= 1) {
- adjustedSteps = steps
- }
-
- // if the distance per step is less than 1, carry out the animation in two steps
- gestureHelper.pinch(
- GestureHelper.Tuple(initLeftX, yCoord),
- GestureHelper.Tuple(initRightX, yCoord),
- GestureHelper.Tuple(finalLeftX, yCoord),
- GestureHelper.Tuple(finalRightX, yCoord),
- adjustedSteps
- )
-
- waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect))
- }
-
/**
* Launches the app through an intent instead of interacting with the launcher and waits until
* the app window is in PIP mode
@@ -250,18 +64,13 @@ open class PipAppHelper(instrumentation: Instrumentation) :
wmHelper,
launchedAppComponentMatcherOverride,
action,
- stringExtras,
- waitConditionsBuilder =
- wmHelper
- .StateSyncBuilder()
- .add(ConditionsFactory.isWMStateComplete())
- .withAppTransitionIdle()
- .add(ConditionsFactory.hasPipWindow())
+ stringExtras
)
wmHelper
.StateSyncBuilder()
.withWindowSurfaceAppeared(this)
+ .add(ConditionsFactory.isWMStateComplete())
.withPipShown()
.waitForAndVerify()
}
@@ -336,126 +145,6 @@ open class PipAppHelper(instrumentation: Instrumentation) :
closePipWindow(WindowManagerStateHelper(instrumentation))
}
- /** Returns the pip window bounds. */
- fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
- val windowRegion = wmHelper.getWindowRegion(this)
- require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
- return windowRegion.bounds
- }
-
- /** Taps the pip window and dismisses it by clicking on the X button. */
- open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the dismiss button
- val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
- uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
- val dismissPipObject =
- uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found")
- val dismissButtonBounds = dismissPipObject.visibleBounds
- uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
-
- // Wait for animation to complete.
- wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
- }
-
- open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the dismiss button
- val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
- uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
- }
-
- /** Close the pip window by pressing the expand button */
- fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- // search and interact with the expand button
- val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
- uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
- val expandPipObject =
- uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found")
- val expandButtonBounds = expandPipObject.visibleBounds
- uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
- wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify()
- }
-
- /** Double click on the PIP window to expand it */
- fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) {
- val windowRect = getWindowRect(wmHelper)
- Log.d(TAG, "First click")
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- Log.d(TAG, "Second click")
- uiDevice.click(windowRect.centerX(), windowRect.centerY())
- Log.d(TAG, "Wait for app transition to end")
- wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
- waitForPipWindowToExpandFrom(wmHelper, Region(windowRect))
- }
-
- private fun waitForPipWindowToExpandFrom(
- wmHelper: WindowManagerStateHelper,
- windowRect: Region
- ) {
- wmHelper
- .StateSyncBuilder()
- .add("pipWindowExpanded") {
- val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- ?: return@add false
- val pipRegion = pipAppWindow.frameRegion
- return@add pipRegion.coversMoreThan(windowRect)
- }
- .waitForAndVerify()
- }
-
- private fun waitForPipWindowToMinimizeFrom(
- wmHelper: WindowManagerStateHelper,
- windowRect: Region
- ) {
- wmHelper
- .StateSyncBuilder()
- .add("pipWindowMinimized") {
- val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- Log.d(TAG, "window " + pipAppWindow)
- if (pipAppWindow == null) return@add false
- val pipRegion = pipAppWindow.frameRegion
- Log.d(
- TAG,
- "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion)
- )
- return@add windowRect.coversMoreThan(pipRegion)
- }
- .waitForAndVerify()
- }
-
- /**
- * Waits until the PIP window snaps horizontally to the provided bounds.
- *
- * @param finalBounds the bounds to wait for PIP window to snap to
- */
- fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) {
- wmHelper
- .StateSyncBuilder()
- .add("pipWindowSnapped") {
- val pipAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- this.windowMatchesAnyOf(window)
- }
- ?: return@add false
- val pipRegionBounds = pipAppWindow.frameRegion.bounds
- return@add pipRegionBounds.left == finalBounds.left &&
- pipRegionBounds.right == finalBounds.right
- }
- .add(ConditionsFactory.isWMStateComplete())
- .waitForAndVerify()
- }
-
companion object {
private const val TAG = "PipAppHelper"
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
@@ -464,8 +153,5 @@ open class PipAppHelper(instrumentation: Instrumentation) :
private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
private const val SOURCE_RECT_HINT = "set_source_rect_hint"
- // minimum number of steps to take, when animating gestures, needs to be 2
- // so that there is at least a single intermediate layer that flicker tests can check
- private const val MIN_STEPS_TO_ANIMATE = 2
}
-}
+} \ No newline at end of file
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
index 69fde0168b14..9e488486e16a 100644
--- 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
@@ -65,10 +65,45 @@ constructor(
.waitForAndVerify()
}
+ fun startSingleAppMediaProjectionWithExtraIntent(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper
+ ) {
+ clickStartMediaProjectionWithExtraIntentButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetApp(targetApp.appName)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+
+ fun startSingleAppMediaProjectionFromRecents(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper,
+ recentTasksIndex: Int = 0,
+ ) {
+ clickStartMediaProjectionButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetAppRecent(recentTasksIndex)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .waitForAndVerify()
+ }
+
private fun clickStartMediaProjectionButton() {
findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() }
}
+ private fun clickStartMediaProjectionWithExtraIntentButton() {
+ findObject(By.res(packageName, START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID)).also { it.click() }
+ }
+
private fun chooseEntireScreenOption() {
findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
@@ -92,6 +127,13 @@ constructor(
findObject(By.text(targetAppName)).also { it.click() }
}
+ private fun selectTargetAppRecent(recentTasksIndex: Int) {
+ // Scroll to to find target app to launch then click app icon it to start capture
+ val recentsTasksRecycler =
+ findObject(By.res(SYSTEMUI_PACKAGE, MEDIA_PROJECTION_RECENT_TASKS))
+ recentsTasksRecycler.children[recentTasksIndex].also{ it.click() }
+ }
+
private fun chooseSingleAppOption() {
findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
@@ -116,8 +158,10 @@ constructor(
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"
+ const val START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID: String = "button_start_mp_new_intent"
val SCREEN_SHARE_OPTIONS_PATTERN: Pattern =
Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)")
+ const val MEDIA_PROJECTION_RECENT_TASKS: String = "media_projection_recent_tasks_recycler"
const val ENTIRE_SCREEN_STRING_RES_NAME: String =
"screen_share_permission_dialog_option_entire_screen"
const val SINGLE_APP_STRING_RES_NAME: String =
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index f891606f0066..7c24a4adca3d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -115,6 +115,19 @@
<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"
@@ -143,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"
@@ -333,12 +347,34 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".BottomHalfPipLaunchingActivity"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="BottomHalfPipLaunchingActivity"
+ 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=".BottomHalfPipActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/TranslucentTheme"
+ android:label="BottomHalfPipActivity"
+ android:exported="true">
+ </activity>
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
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"/>
@@ -349,7 +385,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"/>
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
index 46f01e6c9752..c34d2003ef42 100644
--- 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
@@ -16,17 +16,27 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
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:layout_margin="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:text="Start Media Projection"
android:textAppearance="?android:attr/textAppearanceLarge"/>
+ <Button
+ android:id="@+id/button_start_mp_new_intent"
+ android:layout_margin="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="Start Media Projection with extra intent"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 47d113717ae0..837d050b73ff 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -62,6 +62,12 @@
<item name="android:backgroundDimEnabled">false</item>
</style>
+ <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
<item name="android:windowDisablePreview">true</item>
</style>
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 e4de2c574553..0c1ac9951d32 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,12 @@ 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,
@@ -235,6 +241,21 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".PipActivity");
}
+ public static class BottomHalfPip {
+ public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity";
+ // Test App > Bottom Half PIP Activity
+ public static final String LABEL = "BottomHalfPipActivity";
+
+ // Use the bottom half layout for PIP Activity
+ public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half";
+
+ public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity");
+
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".BottomHalfPipActivity");
+ }
+
public static class SplitScreen {
public static class Primary {
public static final String LABEL = "SplitScreenPrimaryActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
new file mode 100644
index 000000000000..3d4865572486
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.content.res.Configuration;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class BottomHalfPipActivity extends PipActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTheme(R.style.TranslucentTheme);
+ updateLayout();
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateLayout();
+ }
+
+ /**
+ * Sets to match parent layout if the activity is
+ * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half
+ * layout.
+ *
+ * @see #setToBottomHalfMode(boolean)
+ */
+ private void updateLayout() {
+ setToBottomHalfMode(!isInPictureInPictureMode());
+ }
+
+ /**
+ * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the
+ * [LayoutParams.MATCH_PARENT] layout.
+ */
+ private void setToBottomHalfMode(boolean useBottomHalfLayout) {
+ final WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ if (useBottomHalfLayout) {
+ final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds()
+ .height();
+ attrs.y = taskHeight / 2;
+ attrs.height = taskHeight / 2;
+ } else {
+ attrs.y = 0;
+ attrs.height = LayoutParams.MATCH_PARENT;
+ }
+ getWindow().setAttributes(attrs);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
new file mode 100644
index 000000000000..d9d4361411bb
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.content.Intent;
+import android.os.Bundle;
+
+public class BottomHalfPipLaunchingActivity extends SimpleActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = new Intent(this, BottomHalfPipActivity.class);
+ startActivity(intent);
+ }
+}
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/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
index a24a48269d7c..b29b87450197 100644
--- 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
@@ -19,7 +19,8 @@ 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 static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_NORMAL;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_EXTRA_INTENT;
import android.app.Activity;
import android.content.ComponentName;
@@ -71,13 +72,17 @@ public class StartMediaProjectionActivity extends Activity {
setContentView(R.layout.activity_start_media_projection);
Button startMediaProjectionButton = findViewById(R.id.button_start_mp);
+ Button startMediaProjectionButton2 = findViewById(R.id.button_start_mp_new_intent);
startMediaProjectionButton.setOnClickListener(v ->
- startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE));
+ startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE_NORMAL));
+ startMediaProjectionButton2.setOnClickListener(v ->
+ startActivityForResult(mService.createScreenCaptureIntent(),
+ REQUEST_CODE_EXTRA_INTENT));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode != REQUEST_CODE) {
+ if (requestCode != REQUEST_CODE_NORMAL && requestCode != REQUEST_CODE_EXTRA_INTENT) {
throw new IllegalStateException("Unknown request code: " + requestCode);
}
if (resultCode != RESULT_OK) {
@@ -85,6 +90,11 @@ public class StartMediaProjectionActivity extends Activity {
}
Log.d(TAG, "onActivityResult");
startMediaProjectionService(resultCode, data);
+ if (requestCode == REQUEST_CODE_EXTRA_INTENT) {
+ Intent startMain = new Intent(Intent.ACTION_MAIN);
+ startMain.addCategory(Intent.CATEGORY_HOME);
+ startActivity(startMain);
+ }
}
private void startMediaProjectionService(int resultCode, Intent resultData) {
@@ -122,7 +132,7 @@ public class StartMediaProjectionActivity extends Activity {
displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1);
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
- "DanielDisplay",
+ "TestDisplay",
displayBounds.width(),
displayBounds.height(),
DisplayMetrics.DENSITY_HIGH,
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index bacb5eb1cfdf..1f0bd61b5c3f 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -38,6 +38,7 @@ android_test {
"hamcrest-library",
"junit-params",
"kotlin-test",
+ "mockito-kotlin-nodeps",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"platform-screenshot-diff-core",
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
new file mode 100644
index 000000000000..68ec1233cdd7
--- /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 xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <!-- 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"
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ androidprv:keycode="KEYCODE_P"
+ androidprv:modifierState="META" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ androidprv:keycode="KEYCODE_E"
+ androidprv:modifierState="META" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ androidprv:keycode="KEYCODE_C"
+ androidprv:modifierState="META" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ androidprv:keycode="KEYCODE_M"
+ androidprv:modifierState="META" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ androidprv:keycode="KEYCODE_U"
+ androidprv:modifierState="META" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ androidprv:keycode="KEYCODE_B"
+ androidprv:modifierState="META|SHIFT" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ androidprv:keycode="KEYCODE_P"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ androidprv:keycode="KEYCODE_J"
+ shift="true" />
+</bookmarks>
diff --git a/tests/Input/res/xml/bookmarks_legacy.xml b/tests/Input/res/xml/bookmarks_legacy.xml
new file mode 100644
index 000000000000..78cc48b19416
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks_legacy.xml
@@ -0,0 +1,54 @@
+<?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 are legacy way defining bookmarks and we
+ should prefer new way of defining bookmarks as shown in {@link bookmarks.xml} -->
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="p" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <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="p"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+</bookmarks>
diff --git a/tests/Input/res/xml/keyboard_glyph_maps.xml b/tests/Input/res/xml/keyboard_glyph_maps.xml
index d0616ff5ccaa..42561c1a9923 100644
--- a/tests/Input/res/xml/keyboard_glyph_maps.xml
+++ b/tests/Input/res/xml/keyboard_glyph_maps.xml
@@ -19,4 +19,8 @@
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_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
index 072341dcefae..e99c81493394 100644
--- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
@@ -18,20 +18,17 @@ package android.hardware.input
import android.content.Context
import android.content.ContextWrapper
-import android.os.Handler
import android.os.IBinder
-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 org.junit.After
+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.Mock
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.`when`
@@ -69,20 +66,16 @@ class KeyGestureEventHandlerTest {
@get:Rule
val rule = SetFlagsRule()
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
- private val testLooper = TestLooper()
private var registeredListener: IKeyGestureHandler? = 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)
@@ -97,7 +90,7 @@ class KeyGestureEventHandlerTest {
}
registeredListener = listener
null
- }.`when`(iInputManagerMock).registerKeyGestureHandler(any())
+ }.`when`(inputManagerRule.mock).registerKeyGestureHandler(any())
// Handle key gesture handler being unregistered.
doAnswer {
@@ -108,14 +101,7 @@ class KeyGestureEventHandlerTest {
}
registeredListener = null
null
- }.`when`(iInputManagerMock).unregisterKeyGestureHandler(any())
- }
-
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
+ }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(any())
}
private fun handleKeyGestureEvent(event: KeyGestureEvent) {
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
index ca9de6000a5a..cf0bfcc4f6df 100644
--- a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
@@ -26,20 +26,19 @@ 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.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.fail
/**
* Tests for [InputManager.KeyGestureEventListener].
@@ -63,21 +62,18 @@ class KeyGestureEventListenerTest {
@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
- 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)
@@ -92,7 +88,7 @@ class KeyGestureEventListenerTest {
}
registeredListener = listener
null
- }.`when`(iInputManagerMock).registerKeyGestureEventListener(any())
+ }.`when`(inputManagerRule.mock).registerKeyGestureEventListener(any())
// Handle key gesture event listener being unregistered.
doAnswer {
@@ -103,14 +99,7 @@ class KeyGestureEventListenerTest {
}
registeredListener = null
null
- }.`when`(iInputManagerMock).unregisterKeyGestureEventListener(any())
- }
-
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
- }
+ }.`when`(inputManagerRule.mock).unregisterKeyGestureEventListener(any())
}
private fun notifyKeyGestureEvent(event: KeyGestureEvent) {
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/InputDataStoreTests.kt b/tests/Input/src/com/android/server/input/InputDataStoreTests.kt
new file mode 100644
index 000000000000..78c828bafd8f
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputDataStoreTests.kt
@@ -0,0 +1,504 @@
+/*
+ * 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.hardware.input.AppLaunchData
+import android.hardware.input.InputGestureData
+import android.hardware.input.KeyGestureEvent
+import android.platform.test.annotations.Presubmit
+import android.util.AtomicFile
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+
+/**
+ * Tests for {@link InputDataStore}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:InputDataStoreTests
+ */
+@Presubmit
+class InputDataStoreTests {
+
+ companion object {
+ const val USER_ID = 1
+ }
+
+ private lateinit var context: Context
+ private lateinit var inputDataStore: InputDataStore
+ private lateinit var tempFile: File
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ setupInputDataStore()
+ }
+
+ private fun setupInputDataStore() {
+ tempFile = File.createTempFile("input_gestures", ".xml")
+ inputDataStore = InputDataStore(object : InputDataStore.FileInjector("input_gestures") {
+ private val atomicFile: AtomicFile = AtomicFile(tempFile)
+
+ override fun openRead(userId: Int): InputStream? {
+ return atomicFile.openRead()
+ }
+
+ override fun startWrite(userId: Int): FileOutputStream? {
+ return atomicFile.startWrite()
+ }
+
+ override fun finishWrite(userId: Int, fos: FileOutputStream?, success: Boolean) {
+ if (success) {
+ atomicFile.finishWrite(fos)
+ } else {
+ atomicFile.failWrite(fos)
+ }
+ }
+ })
+ }
+
+ private fun getPrintableXml(inputGestures: List<InputGestureData>): String {
+ val outputStream = ByteArrayOutputStream()
+ inputDataStore.writeInputGestureXml(outputStream, true, inputGestures)
+ return outputStream.toString(StandardCharsets.UTF_8).trimIndent()
+ }
+
+ @Test
+ fun saveToDiskKeyGesturesOnly() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build()
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun saveToDiskTouchpadGestures() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun saveToDiskAppLaunchGestures() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER))
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS))
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(
+ AppLaunchData.createLaunchDataForComponent(
+ "com.test",
+ "com.test.BookmarkTest"
+ )
+ )
+ .build()
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun saveToDiskCombinedGestures() {
+ val inputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_9,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS))
+ .build(),
+ )
+
+ inputDataStore.saveInputGestures(USER_ID, inputGestures)
+ assertEquals(
+ inputGestures,
+ inputDataStore.loadInputGestures(USER_ID),
+ getPrintableXml(inputGestures)
+ )
+ }
+
+ @Test
+ fun validXmlParse() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun missingTriggerData() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidKeycode() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9999999" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidTriggerName() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <invalid_trigger_name touchpad_gesture_type="1" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidTouchpadGestureType() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ <input_gesture key_gesture_type="3">
+ <key_trigger keycode="8" modifiers="69632" />
+ </input_gesture>
+ <input_gesture key_gesture_type="21">
+ <key_trigger keycode="9" modifiers="65536" />
+ </input_gesture>
+ <input_gesture key_gesture_type="1">
+ <touchpad_trigger touchpad_gesture_type="9999" />
+ </input_gesture>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val validInputGestures = listOf(
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_1,
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build(),
+ InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_2,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS)
+ .build(),
+ )
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ validInputGestures,
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun emptyInputGestureList() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <input_gesture_list>
+ </input_gesture_list>
+ </root>""".trimIndent()
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ listOf(),
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+
+ @Test
+ fun invalidTag() {
+ val xmlData = """
+ <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+ <root>
+ <invalid_tag_name>
+ </invalid_tag_name>
+ </root>""".trimIndent()
+ val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8))
+ assertEquals(
+ listOf(),
+ inputDataStore.readInputGesturesXml(inputStream, true)
+ )
+ }
+} \ No newline at end of file
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..e281a3fb1287
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -0,0 +1,210 @@
+/*
+ * 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, /* filter = */null)
+ )
+
+ inputGestureManager.removeCustomInputGesture(USER_ID, customGesture)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+ )
+ }
+
+ @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, /* filter = */null)
+ )
+ }
+
+ @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, /* filter = */null)
+ )
+ }
+
+ @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, /* filter = */null)
+ )
+
+ inputGestureManager.removeAllCustomInputGestures(USER_ID, /* filter = */null)
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+ )
+ }
+
+ @Test
+ fun filteringBasedOnTouchpadOrKeyGestures() {
+ val customKeyGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customKeyGesture)
+ val customTouchpadGesture = InputGestureData.Builder()
+ .setTrigger(
+ InputGestureData.createTouchpadTrigger(
+ InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+ )
+ )
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+ .build()
+ inputGestureManager.addCustomInputGesture(USER_ID, customTouchpadGesture)
+
+ assertEquals(
+ listOf(customTouchpadGesture, customKeyGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+ )
+ assertEquals(
+ listOf(customKeyGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID, InputGestureData.Filter.KEY)
+ )
+ assertEquals(
+ listOf(customTouchpadGesture),
+ inputGestureManager.getCustomInputGestures(
+ USER_ID,
+ InputGestureData.Filter.TOUCHPAD
+ )
+ )
+
+ inputGestureManager.removeAllCustomInputGestures(USER_ID, InputGestureData.Filter.KEY)
+ assertEquals(
+ listOf(customTouchpadGesture),
+ inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+ )
+
+ inputGestureManager.removeAllCustomInputGestures(
+ USER_ID,
+ InputGestureData.Filter.TOUCHPAD
+ )
+ assertEquals(
+ listOf<InputGestureData>(),
+ inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+ )
+ }
+} \ 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 351ec4635977..43844f6514e8 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -93,14 +93,15 @@ class InputManagerServiceTests {
)
}
- @JvmField
- @Rule
+ @get:Rule
val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
- .mockStatic(PermissionChecker::class.java).build()!!
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(LocalServices::class.java)
+ .mockStatic(PermissionChecker::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
- @JvmField
- @Rule
+ @get:Rule
val setFlagsRule = SetFlagsRule()
@get:Rule
@@ -124,6 +125,9 @@ class InputManagerServiceTests {
@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
@@ -152,8 +156,7 @@ class InputManagerServiceTests {
}
override fun getKeyboardBacklightController(
- nativeService: NativeInputManagerService?,
- dataStore: PersistentDataStore?
+ nativeService: NativeInputManagerService?
): InputManagerService.KeyboardBacklightControllerInterface {
return kbdController
}
@@ -173,6 +176,9 @@ class InputManagerServiceTests {
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)
@@ -208,6 +214,8 @@ class InputManagerServiceTests {
verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+ verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
+ verify(native).setTouchpadSystemGesturesEnabled(anyBoolean())
verify(native).setShowTouches(anyBoolean())
verify(native).setMotionClassifierEnabled(anyBoolean())
verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
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
index ba360070abc3..fafb0e0f75c8 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -16,29 +16,47 @@
package com.android.server.input
+import android.app.role.RoleManager
import android.content.Context
import android.content.ContextWrapper
-import android.hardware.input.IInputManager
+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.hardware.input.KeyGestureEvent.KeyGestureType
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.util.AtomicFile
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 java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
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
@@ -78,35 +96,104 @@ class KeyGestureControllerTests {
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).build()!!
+ .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 packageManager: PackageManager
+
private var currentPid = 0
- private lateinit var keyGestureController: KeyGestureController
private lateinit var context: Context
+ private lateinit var resources: Resources
+ private lateinit var keyGestureController: KeyGestureController
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
+ private lateinit var tempFile: File
+ private lateinit var inputDataStore: InputDataStore
private var events = mutableListOf<KeyGestureEvent>()
- private var handleEvents = mutableListOf<KeyGestureEvent>()
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+ resources = Mockito.spy(context.resources)
setupInputDevices()
+ setupBehaviors()
testLooper = TestLooper()
currentPid = Process.myPid()
- keyGestureController = KeyGestureController(context, testLooper.looper)
+ tempFile = File.createTempFile("input_gestures", ".xml")
+ inputDataStore =
+ InputDataStore(object : InputDataStore.FileInjector("input_gestures.xml") {
+ private val atomicFile: AtomicFile = AtomicFile(tempFile)
+
+ override fun openRead(userId: Int): InputStream? {
+ return atomicFile.openRead()
+ }
+
+ override fun startWrite(userId: Int): FileOutputStream? {
+ return atomicFile.startWrite()
+ }
+
+ override fun finishWrite(userId: Int, fos: FileOutputStream?, success: Boolean) {
+ if (success) {
+ atomicFile.finishWrite(fos)
+ } else {
+ atomicFile.failWrite(fos)
+ }
+ }
+
+ override fun getAtomicFileForUserId(userId: Int): AtomicFile {
+ return atomicFile
+ }
+ })
+ }
+
+ @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)
+ 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 setupBookmarks(bookmarkRes: Int) {
+ val testBookmarks: XmlResourceParser = context.resources.getXml(bookmarkRes)
+ Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
}
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)
@@ -114,16 +201,29 @@ class KeyGestureControllerTests {
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, inputDataStore)
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+ testLooper.dispatchAll()
}
private fun notifyHomeGestureCompleted() {
- keyGestureController.notifyKeyGestureCompleted(DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
+ keyGestureController.notifyKeyGestureCompleted(
+ DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ )
}
@Test
fun testKeyGestureEvent_registerUnregisterListener() {
+ setupKeyGestureController()
val listener = KeyGestureEventListener()
// Register key gesture event listener
@@ -155,20 +255,22 @@ class KeyGestureControllerTests {
@Test
fun testKeyGestureEvent_multipleGestureHandlers() {
+ setupKeyGestureController()
+
// Set up two callbacks.
var callbackCount1 = 0
var callbackCount2 = 0
var selfCallback = 0
val externalHandler1 = KeyGestureHandler { _, _ ->
- callbackCount1++;
+ callbackCount1++
true
}
val externalHandler2 = KeyGestureHandler { _, _ ->
- callbackCount2++;
+ callbackCount2++
true
}
val selfHandler = KeyGestureHandler { _, _ ->
- selfCallback++;
+ selfCallback++
false
}
@@ -184,7 +286,7 @@ class KeyGestureControllerTests {
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
+ /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null
)
assertEquals(
@@ -211,12 +313,13 @@ class KeyGestureControllerTests {
val expectedKeys: IntArray,
val expectedModifierState: Int,
val expectedActions: IntArray,
+ val expectedAppLaunchData: AppLaunchData? = null,
) {
override fun toString(): String = name
}
@Keep
- private fun keyGestureEventHandlerTestArguments(): Array<TestData> {
+ private fun systemGesturesTestArguments(): Array<TestData> {
return arrayOf(
TestData(
"META + A -> Launch Assistant",
@@ -227,25 +330,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
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(
"META + H -> Go Home",
intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H),
KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
@@ -406,6 +490,495 @@ class KeyGestureControllerTests {
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_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_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_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_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 + 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 + 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(
+ "META + [ -> Resizes a task to fit the left half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_LEFT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ] -> Resizes a task to fit the right half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_RIGHT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + '=' -> Toggles maximization of a task to maximized and restore its bounds",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + '-' -> Minimizes a freeform task",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + '-' -> Magnifier Zoom Out",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + '=' -> Magnifier Zoom In",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + M -> Toggle Magnification",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_M
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + S -> Activate Select to Speak",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_S
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON or 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.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ 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.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ 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 bookmarkArguments(): Array<TestData> {
+ return arrayOf(
+ 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 + P -> Launch Default Contacts",
+ 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_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 + C -> Launch Default Calendar",
+ 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_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 + 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 + P -> Launch Default Contacts",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_P
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_P),
+ 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")
+ )
+ )
+ }
+
+ @Test
+ @Parameters(method = "bookmarkArguments")
+ fun testBookmarks(test: TestData) {
+ setupBookmarks(com.android.test.input.R.xml.bookmarks)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ @Parameters(method = "bookmarkArguments")
+ fun testBookmarksLegacy(test: TestData) {
+ setupBookmarks(com.android.test.input.R.xml.bookmarks_legacy)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @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,
@@ -494,35 +1067,285 @@ class KeyGestureControllerTests {
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),
+ "SYSRQ -> Take screenshot",
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
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),
+ "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(
- "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,
+ "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)
+ ),
+ TestData(
+ "LOCK -> Lock Screen",
+ intArrayOf(KeyEvent.KEYCODE_LOCK),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+ intArrayOf(KeyEvent.KEYCODE_LOCK),
+ 0,
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,
+ "FULLSCREEN -> Maximizes a task to fit the screen",
+ intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ )
+ }
+
+ @Test
+ @Parameters(method = "systemKeysTestArguments")
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ 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,
+ KeyEvent.KEYCODE_DO_NOT_DISTURB,
+ KeyEvent.KEYCODE_LOCK,
+ KeyEvent.KEYCODE_FULLSCREEN
+ )
+
+ 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
@@ -532,24 +1355,240 @@ class KeyGestureControllerTests {
}
@Test
- @Parameters(method = "keyGestureEventHandlerTestArguments")
- fun testKeyGestures(test: TestData) {
+ @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_KEY_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)
+ }
+
+ @Test
+ @Parameters(method = "customInputGesturesTestArguments")
+ fun testCustomKeyGesturesSavedAndLoadedByController(test: TestData) {
+ val userId = 10
+ 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.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ keyGestureController.addCustomInputGesture(userId, inputGestureData.aidlData)
+ testLooper.dispatchAll()
+
+ // Reinitialize the gesture controller simulating a login/logout for the user.
+ setupKeyGestureController()
+ keyGestureController.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null)
+ assertEquals(
+ "Test: $test doesn't produce correct number of saved input gestures",
+ 1,
+ savedInputGestures.size
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct input gesture data", inputGestureData,
+ InputGestureData(savedInputGestures[0])
+ )
+ }
+
+ 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, _ ->
- handleEvents.add(KeyGestureEvent(event))
+ handledEvents.add(KeyGestureEvent(event))
true
}
keyGestureController.registerKeyGestureHandler(handler, 0)
- handleEvents.clear()
+ handledEvents.clear()
- sendKeys(test.keys, /* assertAllConsumed = */ false)
+ 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)
+ }
+
+ @Test
+ @Parameters(method = "customTouchpadGesturesTestArguments")
+ fun testCustomTouchpadGesturesSavedAndLoadedByController(test: TouchpadTestData) {
+ val userId = 10
+ 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.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ keyGestureController.addCustomInputGesture(userId, inputGestureData.aidlData)
+ testLooper.dispatchAll()
+
+ // Reinitialize the gesture controller simulating a login/logout for the user.
+ setupKeyGestureController()
+ keyGestureController.setCurrentUserId(userId)
+ testLooper.dispatchAll()
+ val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null)
+ assertEquals(
+ "Test: $test doesn't produce correct number of saved input gestures",
+ 1,
+ savedInputGestures.size
+ )
+ assertEquals(
+ "Test: $test doesn't produce correct input gesture data", inputGestureData,
+ InputGestureData(savedInputGestures[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,
- handleEvents.size
+ handledEvents.size
)
- for (i in handleEvents.indices) {
- val event = handleEvents[i]
+ for (i in handledEvents.indices) {
+ val event = handledEvents[i]
assertArrayEquals(
"Test: $test doesn't produce correct key gesture keycodes",
test.expectedKeys,
@@ -570,60 +1609,39 @@ class KeyGestureControllerTests {
test.expectedActions[i],
event.action
)
+ assertEquals(
+ "Test: $test doesn't produce correct app launch data",
+ test.expectedAppLaunchData,
+ event.appLaunchData
+ )
}
keyGestureController.unregisterKeyGestureHandler(handler, 0)
}
- @Test
- fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
- 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 }
+ 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()
- for (key in testKeys) {
- sendKeys(intArrayOf(key), /* assertAllConsumed = */ true)
- }
+ sendKeys(testKeys)
+ assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.size)
}
- private fun sendKeys(testKeys: IntArray, assertAllConsumed: Boolean) {
+ private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) {
var metaState = 0
+ val now = SystemClock.uptimeMillis()
for (key in testKeys) {
val downEvent = KeyEvent(
- /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_DOWN, key,
- 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
- 0 /*flags*/, InputDevice.SOURCE_KEYBOARD
+ now, now, KeyEvent.ACTION_DOWN, key, 0 /*repeat*/, metaState,
+ DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
+ InputDevice.SOURCE_KEYBOARD
)
- val consumed =
- keyGestureController.interceptKeyBeforeDispatching(null, downEvent, 0) == -1L
- if (assertAllConsumed) {
- assertTrue(
- "interceptKeyBeforeDispatching should consume all events $downEvent",
- consumed
- )
- }
+ interceptKey(downEvent, assertNotSentToApps)
metaState = metaState or MODIFIER.getOrDefault(key, 0)
downEvent.recycle()
@@ -632,24 +1650,35 @@ class KeyGestureControllerTests {
for (key in testKeys.reversed()) {
val upEvent = KeyEvent(
- /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_UP, key,
- 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
- 0 /*flags*/, InputDevice.SOURCE_KEYBOARD
+ now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState,
+ DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
+ InputDevice.SOURCE_KEYBOARD
)
- val consumed =
- keyGestureController.interceptKeyBeforeDispatching(null, upEvent, 0) == -1L
- if (assertAllConsumed) {
- assertTrue(
- "interceptKeyBeforeDispatching should consume all events $upEvent",
- consumed
- )
- }
+ 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))
@@ -667,4 +1696,4 @@ class KeyGestureControllerTests {
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..644d5a0679de 100644
--- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -19,25 +19,27 @@ package com.android.server.input
import android.animation.ValueAnimator
import android.content.Context
import android.content.ContextWrapper
+import android.content.res.Resources
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.SystemProperties
import android.os.UEventObserver
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
+import android.util.TypedValue
import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.R
+import com.android.modules.utils.testing.ExtendedMockitoRule
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 org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@@ -47,15 +49,11 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
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()
@@ -96,22 +94,24 @@ class KeyboardBacklightControllerTests {
const val LIGHT_ID = 2
const val SECOND_LIGHT_ID = 3
const val MAX_BRIGHTNESS = 255
+ const val USER_INACTIVITY_THRESHOLD_MILLIS = 30000
}
@get:Rule
- val rule = MockitoJUnit.rule()!!
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!!
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
@Mock
- private lateinit var iInputManager: IInputManager
- @Mock
private lateinit var native: NativeInputManagerService
@Mock
private lateinit var uEventManager: UEventManager
+ @Mock
+ private lateinit var resources: Resources
private lateinit var keyboardBacklightController: KeyboardBacklightController
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
@@ -120,24 +120,12 @@ class KeyboardBacklightControllerTests {
@Before
fun setup() {
context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
- override fun openRead(): InputStream? {
- throw FileNotFoundException()
- }
-
- override fun startWrite(): FileOutputStream? {
- throw IOException()
- }
-
- override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
- })
+ `when`(context.resources).thenReturn(resources)
testLooper = TestLooper()
- keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
- testLooper.looper, FakeAnimatorFactory(), uEventManager)
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+ setupConfig()
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,270 +140,191 @@ class KeyboardBacklightControllerTests {
}
}
- @After
- fun tearDown() {
- if (this::inputManagerGlobalSession.isInitialized) {
- inputManagerGlobalSession.close()
+ private fun setupConfig() {
+ val brightnessValues = intArrayOf(100, 200, 0)
+ val decreaseThresholds = intArrayOf(-1, 900, 1900)
+ val increaseThresholds = intArrayOf(1000, 2000, -1)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues))
+ .thenReturn(brightnessValues)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold))
+ .thenReturn(decreaseThresholds)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold))
+ .thenReturn(increaseThresholds)
+ `when`(resources.getInteger(R.integer.config_keyboardBacklightTimeoutMs))
+ .thenReturn(USER_INACTIVITY_THRESHOLD_MILLIS)
+ `when`(
+ resources.getValue(
+ eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant),
+ any(TypedValue::class.java),
+ anyBoolean()
+ )
+ ).then {
+ val args = it.arguments
+ val outValue = args[1] as TypedValue
+ outValue.data = java.lang.Float.floatToRawIntBits(1.0f)
+ Unit
}
}
+ private fun setupController() {
+ keyboardBacklightController = KeyboardBacklightController(context, native,
+ testLooper.looper, FakeAnimatorFactory(), uEventManager)
+ }
+
@Test
fun testKeyboardBacklightIncrementDecrement() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
}
@Test
fun testKeyboardWithoutBacklight() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
- }
+ setupController()
+ val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
+ val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
}
@Test
fun testKeyboardWithMultipleLight() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- 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(
- listOf(
- keyboardBacklight,
- keyboardInputLight
- )
- )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
- assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
- assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
- }
- }
-
- @Test
- fun testRestoreBacklightOnInputDeviceAdded() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).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))
-
- for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the " +
- "data store",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
- }
- }
- }
-
- @Test
- fun testRestoreBacklightOnInputDeviceChanged() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertTrue(
- "Keyboard backlight should not be changed until its added",
- lightColorMap.isEmpty()
+ setupController()
+ 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`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(
+ listOf(
+ keyboardBacklight,
+ keyboardInputLight
)
+ )
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data store",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
+ assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
+ assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
}
@Test
fun testKeyboardBacklight_registerUnregisterListener() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).use {
- 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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- // Register backlight listener
- val listener = KeyboardBacklightListener()
- keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
-
- lastBacklightState = null
- keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
- testLooper.dispatchNext()
+ setupController()
+ 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`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ // Register backlight listener
+ val listener = KeyboardBacklightListener()
+ keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+
+ lastBacklightState = null
+ keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+ testLooper.dispatchNext()
- assertEquals(
- "Backlight state device Id should be $DEVICE_ID",
- DEVICE_ID,
- lastBacklightState!!.deviceId
- )
- assertEquals(
- "Backlight state brightnessLevel should be 1",
- 1,
- lastBacklightState!!.brightnessLevel
- )
- assertEquals(
- "Backlight state maxBrightnessLevel should be $maxLevel",
- maxLevel,
- lastBacklightState!!.maxBrightnessLevel
- )
- assertEquals(
- "Backlight state isTriggeredByKeyPress should be true",
- true,
- lastBacklightState!!.isTriggeredByKeyPress
- )
+ assertEquals(
+ "Backlight state device Id should be $DEVICE_ID",
+ DEVICE_ID,
+ lastBacklightState!!.deviceId
+ )
+ assertEquals(
+ "Backlight state brightnessLevel should be 1",
+ 1,
+ lastBacklightState!!.brightnessLevel
+ )
+ assertEquals(
+ "Backlight state maxBrightnessLevel should be $maxLevel",
+ maxLevel,
+ lastBacklightState!!.maxBrightnessLevel
+ )
+ assertEquals(
+ "Backlight state isTriggeredByKeyPress should be true",
+ true,
+ lastBacklightState!!.isTriggeredByKeyPress
+ )
- // Unregister listener
- keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+ // Unregister listener
+ keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
- lastBacklightState = null
- incrementKeyboardBacklight(DEVICE_ID)
+ lastBacklightState = null
+ incrementKeyboardBacklight(DEVICE_ID)
- assertNull("Listener should not receive any updates", lastBacklightState)
- }
+ assertNull("Listener should not receive any updates", lastBacklightState)
}
@Test
fun testKeyboardBacklight_userActivity() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).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))
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data store",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertNotEquals(
+ "Keyboard backlight level should be incremented to a non-zero value",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
- testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be turned off after inactivity",
- 0,
- lightColorMap[LIGHT_ID]
- )
- }
+ testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong())
+ testLooper.dispatchNext()
+ assertEquals(
+ "Keyboard backlight level should be turned off after inactivity",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testKeyboardBacklight_displayOnOff() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).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))
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+
+ val currentValue = lightColorMap[LIGHT_ID]
+ assertNotEquals(
+ "Keyboard backlight level should be incremented to a non-zero value",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data " +
- "store when display turned on",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+ assertEquals(
+ "Keyboard backlight level should be turned off after display is turned off",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
- keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
- assertEquals(
- "Keyboard backlight level should be turned off after display is turned off",
- 0,
- lightColorMap[LIGHT_ID]
- )
- }
+ keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+ assertEquals(
+ "Keyboard backlight level should be turned on after display is turned on",
+ currentValue,
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() {
+ setupController()
var counter = sysfsNodeChanges
keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent(
"ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000"
@@ -475,249 +384,160 @@ class KeyboardBacklightControllerTests {
@Test
@UiThreadTest
fun testKeyboardBacklightAnimation_onChangeLevels() {
- KeyboardBacklightFlags(
- animationEnabled = true,
- customLevelsEnabled = false,
- ambientControlEnabled = false
- ).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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Should start animation from level 0",
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
- lastAnimationValues[0]
- )
- assertEquals(
- "Should start animation to level 1",
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
- lastAnimationValues[1]
- )
+ ExtendedMockito.doReturn("true").`when` {
+ SystemProperties.get(eq("persist.input.keyboard.backlight_animation.enabled"))
}
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `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(
+ "Should start animation from level 0",
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
+ lastAnimationValues[0]
+ )
+ assertEquals(
+ "Should start animation to level 1",
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
+ lastAnimationValues[1]
+ )
}
@Test
fun testKeyboardBacklightPreferredLevels() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- 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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- suggestedLevels)
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, suggestedLevels)
}
@Test
fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- 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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
}
@Test
fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- 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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- // Framework will add the lowest and maximum levels if not provided via config
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- intArrayOf(0, 22, 63, 135, 196, 255))
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = intArrayOf(22, 63, 135, 196)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `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
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ intArrayOf(0, 22, 63, 135, 196, 255))
}
@Test
fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = true,
- ambientControlEnabled = false
- ).use {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- 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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- // Framework will drop out of bound levels in the config
- assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
- intArrayOf(0, 22, 63, 135, 196, 255))
- }
- }
-
- @Test
- fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).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))
-
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertNull(
- "Keyboard backlight level should not be restored to the saved level",
- lightColorMap[LIGHT_ID]
- )
- }
- }
-
- @Test
- fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).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))
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- incrementKeyboardBacklight(DEVICE_ID)
- assertFalse(
- "Light value should not be backed up if ambient control is enabled",
- dataStore.getKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor, LIGHT_ID
- ).isPresent
- )
- }
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+ suggestedLevels)
+ `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
+ assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+ intArrayOf(0, 22, 63, 135, 196, 255))
}
@Test
fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- sendAmbientBacklightValue(1)
- assertEquals(
- "Light value should be changed to ambient provided value",
- Color.argb(1, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ sendAmbientBacklightValue(1)
+ assertEquals(
+ "Light value should be changed to ambient provided value",
+ Color.argb(1, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- incrementKeyboardBacklight(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Light value for level after increment post Ambient change is mismatched",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ assertEquals(
+ "Light value for level after increment post Ambient change is mismatched",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- sendAmbientBacklightValue(254)
- assertEquals(
- "Light value should be changed to ambient provided value",
- Color.argb(254, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ sendAmbientBacklightValue(254)
+ assertEquals(
+ "Light value should be changed to ambient provided value",
+ Color.argb(254, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- decrementKeyboardBacklight(DEVICE_ID)
+ decrementKeyboardBacklight(DEVICE_ID)
- val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
- assertEquals(
- "Light value for level after decrement post Ambient change is mismatched",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
+ assertEquals(
+ "Light value for level after decrement post Ambient change is mismatched",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
}
@Test
fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
- KeyboardBacklightFlags(
- animationEnabled = false,
- customLevelsEnabled = false,
- ambientControlEnabled = true
- ).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))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Light value should be changed to the first level",
- Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ setupController()
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `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(
+ "Light value should be changed to the first level",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- sendAmbientBacklightValue(100)
- assertNotEquals(
- "Light value should not change based on ambient changes after manual changes",
- Color.argb(100, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- }
+ sendAmbientBacklightValue(100)
+ assertNotEquals(
+ "Light value should not change based on ambient changes after manual changes",
+ Color.argb(100, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
}
private fun assertIncrementDecrementForLevels(
@@ -734,11 +554,6 @@ class KeyboardBacklightControllerTests {
Color.argb(expectedLevels[level], 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for level $level must be correctly stored in the datastore",
- expectedLevels[level],
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
}
// Increment above max level
@@ -748,11 +563,6 @@ class KeyboardBacklightControllerTests {
Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for max level must be correctly stored in the datastore",
- MAX_BRIGHTNESS,
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
for (level in expectedLevels.size - 2 downTo 0) {
decrementKeyboardBacklight(deviceId)
@@ -761,11 +571,6 @@ class KeyboardBacklightControllerTests {
Color.argb(expectedLevels[level], 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for level $level must be correctly stored in the datastore",
- expectedLevels[level],
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
}
// Decrement below min level
@@ -775,11 +580,6 @@ class KeyboardBacklightControllerTests {
Color.argb(0, 0, 0, 0),
lightColorMap[lightId]
)
- assertEquals(
- "Light value for min level must be correctly stored in the datastore",
- 0,
- dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
- )
}
inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
@@ -822,23 +622,6 @@ class KeyboardBacklightControllerTests {
val isTriggeredByKeyPress: Boolean
)
- private inner class KeyboardBacklightFlags constructor(
- animationEnabled: Boolean,
- customLevelsEnabled: Boolean,
- ambientControlEnabled: Boolean
- ) : AutoCloseable {
- init {
- InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
- InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
- InputFeatureFlagProvider
- .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
- }
-
- override fun close() {
- InputFeatureFlagProvider.clearOverrides()
- }
- }
-
private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
lastAnimationValues[0] = from
diff --git a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
index c073c7aae678..5da0beb9cc8a 100644
--- a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt
@@ -23,9 +23,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
-import android.hardware.input.IInputManager
import android.hardware.input.InputManager
-import android.hardware.input.InputManagerGlobal
import android.hardware.input.KeyGlyphMap.KeyCombination
import android.os.Bundle
import android.os.test.TestLooper
@@ -36,8 +34,8 @@ 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.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@@ -61,34 +59,31 @@ class KeyboardGlyphManagerTests {
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"
}
- @JvmField
- @Rule(order = 0)
+ @get:Rule
val setFlagsRule = SetFlagsRule()
-
- @JvmField
- @Rule(order = 1)
+ @get:Rule
val mockitoRule = MockitoJUnit.rule()!!
+ @get:Rule
+ val inputManagerRule = MockInputManagerRule()
@Mock
private lateinit var packageManager: PackageManager
- @Mock
- private lateinit var iInputManager: IInputManager
-
private lateinit var keyboardGlyphManager: KeyboardGlyphManager
private lateinit var context: Context
private lateinit var testLooper: TestLooper
- private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var keyboardDevice: InputDevice
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
testLooper = TestLooper()
keyboardGlyphManager = KeyboardGlyphManager(context, testLooper.looper)
@@ -98,21 +93,17 @@ class KeyboardGlyphManagerTests {
testLooper.dispatchAll()
}
- @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)))
.thenReturn(inputManager)
keyboardDevice = createKeyboard(DEVICE_ID, VENDOR_ID, PRODUCT_ID, 0, "", "")
- Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
- Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+ 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() {
@@ -158,6 +149,10 @@ class KeyboardGlyphManagerTests {
"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)
@@ -173,6 +168,7 @@ class KeyboardGlyphManagerTests {
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)
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 301c0e6a159f..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,10 +56,6 @@ 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
fun createKeyboard(
deviceId: 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/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b5258dfc9c3c..60fa52f85e34 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -402,4 +402,73 @@ public class TouchpadDebugViewTest {
// Verify that no updateViewLayout is called (as expected for a two-finger drag gesture).
verify(mWindowManager, times(0)).updateViewLayout(any(), any());
}
-} \ No newline at end of file
+
+ @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/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/Internal/Android.bp b/tests/Internal/Android.bp
index 3e58517579b8..9f35c7b7fa33 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -32,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: [
@@ -45,3 +66,9 @@ android_ravenwood_test {
],
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/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/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/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 096555eb3056..8be74eaccd20 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -35,7 +35,13 @@ android_test {
"services.core",
"services.net",
"truth",
- ],
+ ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
+ "true": [
+ "service-crashrecovery-pre-jarjar",
+ "framework-crashrecovery.impl",
+ ],
+ default: [],
+ }),
libs: ["android.test.runner.stubs.system"],
jni_libs: [
// mockito-target-extended dependencies
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index c0e90f9232d6..49616c30b784 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -83,6 +83,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -112,6 +113,7 @@ public class CrashRecoveryTest {
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -138,8 +140,10 @@ public class CrashRecoveryTest {
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
- Manifest.permission.WRITE_DEVICE_CONFIG);
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -224,39 +228,45 @@ public class CrashRecoveryTest {
PackageWatchdog watchdog = createWatchdog();
RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
- verify(rescuePartyObserver).executeBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(2);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(3);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(4);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(5);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(6);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
}
@Test
@@ -265,14 +275,15 @@ public class CrashRecoveryTest {
RollbackPackageHealthObserver rollbackObserver =
setUpRollbackPackageHealthObserver(watchdog);
- verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
- verify(rollbackObserver).executeBootLoopMitigation(1);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rollbackObserver).onExecuteBootLoopMitigation(1);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
// Update the list of available rollbacks after executing bootloop mitigation once
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
@@ -280,15 +291,17 @@ public class CrashRecoveryTest {
watchdog.noteBoot();
- verify(rollbackObserver).executeBootLoopMitigation(2);
- verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rollbackObserver).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
// Update the list of available rollbacks after executing bootloop mitigation once
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
watchdog.noteBoot();
- verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
}
@Test
@@ -299,61 +312,69 @@ public class CrashRecoveryTest {
RollbackPackageHealthObserver rollbackObserver =
setUpRollbackPackageHealthObserver(watchdog);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
- verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
- verify(rescuePartyObserver).executeBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
- verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(2);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
- verify(rollbackObserver).executeBootLoopMitigation(1);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
+ verify(rollbackObserver).onExecuteBootLoopMitigation(1);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
// Update the list of available rollbacks after executing bootloop mitigation once
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
ROLLBACK_INFO_MANUAL));
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(3);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(4);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(5);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
- verify(rollbackObserver).executeBootLoopMitigation(2);
- verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
+ verify(rollbackObserver).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
// Update the list of available rollbacks after executing bootloop mitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(6);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
- verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
Mockito.reset(rescuePartyObserver);
@@ -361,8 +382,9 @@ public class CrashRecoveryTest {
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
- verify(rescuePartyObserver).executeBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@Test
@@ -373,37 +395,41 @@ public class CrashRecoveryTest {
RollbackPackageHealthObserver rollbackObserver =
setUpRollbackPackageHealthObserver(watchdog);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
- verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
- verify(rescuePartyObserver).executeBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
- verify(rollbackObserver, never()).executeBootLoopMitigation(1);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
- verify(rollbackObserver).executeBootLoopMitigation(1);
- verify(rollbackObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver).onExecuteBootLoopMitigation(1);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
// Update the list of available rollbacks after executing bootloop mitigation once
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
ROLLBACK_INFO_MANUAL));
watchdog.noteBoot();
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
- verify(rollbackObserver).executeBootLoopMitigation(2);
- verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver).onExecuteBootLoopMitigation(2);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
// Update the list of available rollbacks after executing bootloop mitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
watchdog.noteBoot();
- verify(rescuePartyObserver).executeBootLoopMitigation(2);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
- verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
+ verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
Mockito.reset(rescuePartyObserver);
@@ -411,8 +437,9 @@ public class CrashRecoveryTest {
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
- verify(rescuePartyObserver).executeBootLoopMitigation(1);
- verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
+ mTestLooper.dispatchAll();
+ verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@Test
@@ -435,46 +462,46 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: SCOPED_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).execute(versionedPackageA,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).execute(versionedPackageA,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: ALL_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).execute(versionedPackageA,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rollbackObserver, never()).execute(versionedPackageA,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: WARM_REBOOT
- verify(rescuePartyObserver).execute(versionedPackageA,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).execute(versionedPackageA,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: Low impact rollback
- verify(rollbackObserver).execute(versionedPackageA,
+ verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
// update available rollbacks to mock rollbacks being applied after the call to
- // rollbackObserver.execute
+ // rollbackObserver.onExecuteHealthCheckMitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(
List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
@@ -482,9 +509,9 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).execute(versionedPackageA,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
}
@@ -510,24 +537,24 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: WARM_REBOOT
- verify(rescuePartyObserver).execute(versionedPackageA,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).execute(versionedPackageA,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: Low impact rollback
- verify(rollbackObserver).execute(versionedPackageA,
+ verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
// update available rollbacks to mock rollbacks being applied after the call to
- // rollbackObserver.execute
+ // rollbackObserver.onExecuteHealthCheckMitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(
List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
@@ -535,9 +562,9 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied
- verify(rescuePartyObserver, never()).execute(versionedPackageA,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).execute(versionedPackageA,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
}
@@ -567,48 +594,48 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: SCOPED_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: ALL_DEVICE_CONFIG_RESET
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: WARM_REBOOT
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: Low impact rollback
- verify(rollbackObserver).execute(versionedPackageUi,
+ verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
// update available rollbacks to mock rollbacks being applied after the call to
- // rollbackObserver.execute
+ // rollbackObserver.onExecuteHealthCheckMitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(
List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
@@ -616,44 +643,44 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 4);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 5);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 6);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 7);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 8);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
}
@@ -685,26 +712,26 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: WARM_REBOOT
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: Low impact rollback
- verify(rollbackObserver).execute(versionedPackageUi,
+ verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
// update available rollbacks to mock rollbacks being applied after the call to
- // rollbackObserver.execute
+ // rollbackObserver.onExecuteHealthCheckMitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(
List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
@@ -712,30 +739,40 @@ public class CrashRecoveryTest {
Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH);
// Mitigation: Factory reset. High impact rollbacks are performed only for boot loops.
- verify(rescuePartyObserver).execute(versionedPackageUi,
+ verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
- verify(rescuePartyObserver, never()).execute(versionedPackageUi,
+ verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 3);
- verify(rollbackObserver, never()).execute(versionedPackageUi,
+ verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 2);
}
RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
RollbackPackageHealthObserver rollbackObserver =
- spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager));
+ spy(new RollbackPackageHealthObserver(mSpyContext));
when(mSpyContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW,
ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL));
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
-
- watchdog.registerHealthObserver(rollbackObserver);
+ 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, mTestExecutor);
return rollbackObserver;
}
RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
setCrashRecoveryPropRescueBootCount(0);
RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
assertFalse(RescueParty.isRebootPropertySet());
- watchdog.registerHealthObserver(rescuePartyObserver);
+ watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor);
return rescuePartyObserver;
}
@@ -775,7 +812,7 @@ public class CrashRecoveryTest {
Handler handler = new Handler(mTestLooper.getLooper());
PackageWatchdog watchdog =
new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
- mConnectivityModuleConnector, mTestClock);
+ mTestClock);
mockCrashRecoveryProperties(watchdog);
// Verify controller is not automatically started
@@ -787,8 +824,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;
@@ -1001,7 +1040,7 @@ public class CrashRecoveryTest {
triggerFailureCount = 1;
}
for (int i = 0; i < triggerFailureCount; i++) {
- watchdog.onPackageFailure(packages, failureReason);
+ watchdog.notifyPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
if (Flags.recoverabilityDetection()) {
diff --git a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
index 2fbfeba47b13..055e159ff0b6 100644
--- a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
@@ -50,7 +52,7 @@ public class ExplicitHealthCheckServiceTest {
IBinder binder = mExplicitHealthCheckService.onBind(new Intent());
CountDownLatch countDownLatch = new CountDownLatch(1);
RemoteCallback callback = new RemoteCallback(result -> {
- assertThat(result.get(ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE))
+ assertThat(result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE))
.isEqualTo(PACKAGE_NAME);
countDownLatch.countDown();
});
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 5b178250a4c9..c64dc7296f0a 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -46,10 +46,14 @@ 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;
import android.util.LongArrayQueue;
+import android.util.Slog;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -85,6 +89,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -111,8 +116,12 @@ 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 Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -146,8 +155,10 @@ public class PackageWatchdogTest {
new File(InstrumentationRegistry.getContext().getFilesDir(),
"package-watchdog.xml").delete();
adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
- Manifest.permission.WRITE_DEVICE_CONFIG);
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -219,7 +230,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -235,8 +247,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -253,7 +267,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -269,8 +284,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -287,7 +304,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -303,8 +321,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -323,13 +343,14 @@ public class PackageWatchdogTest {
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
// Start observing APP_A
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time half-way
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
// Start observing APP_A again
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time such that it should have expired were it not for the second observation
moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
@@ -351,15 +372,17 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer1, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer2, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
// Then advance time and run IO Handler so file is saved
mTestLooper.dispatchAll();
// Then start a new watchdog
PackageWatchdog watchdog2 = createWatchdog();
// Then resume observer1 and observer2
- watchdog2.registerHealthObserver(observer1);
- watchdog2.registerHealthObserver(observer2);
+ watchdog2.registerHealthObserver(observer1, mTestExecutor);
+ watchdog2.registerHealthObserver(observer2, mTestExecutor);
raiseFatalFailureAndDispatch(watchdog2,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -367,6 +390,7 @@ public class PackageWatchdogTest {
// We should receive failed packages as expected to ensure observers are persisted and
// resumed correctly
+ mTestLooper.dispatchAll();
assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
@@ -380,12 +404,14 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A below the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
@@ -407,9 +433,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -441,7 +468,8 @@ public class PackageWatchdogTest {
}
};
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -470,13 +498,17 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -516,13 +548,17 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -570,8 +606,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -634,8 +672,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -702,8 +742,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -724,8 +766,10 @@ public class PackageWatchdogTest {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -755,8 +799,10 @@ public class PackageWatchdogTest {
// Start observing with explicit health checks for APP_A and APP_B respectively
// with observer1 and observer2
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -772,7 +818,8 @@ public class PackageWatchdogTest {
// Observer3 didn't exist when we got the explicit health check above, so
// it starts out with a non-passing explicit health check and has to wait for a pass
// otherwise it would be notified of APP_A failure on expiry
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION);
// Then expire observers
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -802,8 +849,9 @@ public class PackageWatchdogTest {
// Start observing with explicit health checks for APP_A and APP_B
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -839,7 +887,7 @@ public class PackageWatchdogTest {
// Then set new supported packages
controller.setSupportedPackages(Arrays.asList(APP_C));
// Start observing APP_A and APP_C; only APP_C has support for explicit health checks
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
// Run handler so requests/cancellations are dispatched to the controller
mTestLooper.dispatchAll();
@@ -870,7 +918,8 @@ public class PackageWatchdogTest {
// package observation duration == LONG_DURATION
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION);
// Then APP_A has exceeded health check duration
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -901,7 +950,8 @@ public class PackageWatchdogTest {
// package observation duration == SHORT_DURATION / 2
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
// Forward time to expire the observation duration
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
@@ -966,6 +1016,7 @@ public class PackageWatchdogTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY)
public void testNetworkStackFailure() {
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
final PackageWatchdog wd = createWatchdog();
@@ -973,7 +1024,7 @@ public class PackageWatchdogTest {
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -986,13 +1037,14 @@ public class PackageWatchdogTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY)
public void testNetworkStackFailureRecoverabilityDetection() {
final PackageWatchdog wd = createWatchdog();
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1013,17 +1065,18 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Fail APP_A below the threshold which should not trigger package failures
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mHealthCheckFailedPackages).isEmpty();
// One more to trigger the package failure
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
@@ -1041,11 +1094,12 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1053,10 +1107,10 @@ public class PackageWatchdogTest {
// DEFAULT_TRIGGER_FAILURE_DURATION_MS.
assertThat(observer.mHealthCheckFailedPackages).isEmpty();
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1066,15 +1120,16 @@ public class PackageWatchdogTest {
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_beforeExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
// Note: Don't move too close to the expiration time otherwise the handler will be thrashed
// by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
// small timeouts.
@@ -1088,15 +1143,16 @@ public class PackageWatchdogTest {
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_afterExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1118,19 +1174,20 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
// Raise 2 failures at t=0 and t=900 respectively
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(900);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
// Raise 2 failures at t=1100
moveTimeForwardAndDispatch(200);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
- watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1145,8 +1202,10 @@ public class PackageWatchdogTest {
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
@@ -1165,7 +1224,8 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
@@ -1185,7 +1245,8 @@ public class PackageWatchdogTest {
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(true);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1203,7 +1264,8 @@ public class PackageWatchdogTest {
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(false);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1214,13 +1276,15 @@ public class PackageWatchdogTest {
/** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
@Test
public void testBootLoopDetection_meetsThreshold() {
+ Slog.w("hrm1243", "I should definitely be here try 1 ");
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1228,10 +1292,11 @@ public class PackageWatchdogTest {
public void testBootLoopDetection_meetsThresholdRecoverability() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1243,10 +1308,11 @@ public class PackageWatchdogTest {
public void testBootLoopDetection_doesNotMeetThreshold() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1259,10 +1325,11 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1277,11 +1344,12 @@ public class PackageWatchdogTest {
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1293,11 +1361,12 @@ public class PackageWatchdogTest {
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1310,7 +1379,7 @@ public class PackageWatchdogTest {
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
watchdog.noteBoot();
@@ -1324,7 +1393,7 @@ public class PackageWatchdogTest {
watchdog.noteBoot();
}
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1333,7 +1402,7 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
watchdog.noteBoot();
}
@@ -1349,7 +1418,7 @@ public class PackageWatchdogTest {
for (int i = 0; i < 4; i++) {
watchdog.noteBoot();
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1361,7 +1430,8 @@ public class PackageWatchdogTest {
public void testNullFailedPackagesList() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION);
raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
assertThat(observer1.mMitigatedPackages).isEmpty();
@@ -1379,18 +1449,18 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog(testController, true);
TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(testObserver1);
- watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.registerHealthObserver(testObserver2);
- watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
- watchdog.registerHealthObserver(testObserver3);
- watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION);
mTestLooper.dispatchAll();
watchdog.unregisterHealthObserver(testObserver1);
@@ -1422,15 +1492,16 @@ public class PackageWatchdogTest {
public void testFailureHistoryIsPreserved() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION);
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
- watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEmpty();
- watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
- watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION);
+ watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEqualTo(List.of(APP_A));
@@ -1444,7 +1515,8 @@ public class PackageWatchdogTest {
public void testMitigationSlidingWindow() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A),
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A),
PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2);
@@ -1728,7 +1800,7 @@ public class PackageWatchdogTest {
triggerFailureCount = 1;
}
for (int i = 0; i < triggerFailureCount; i++) {
- watchdog.onPackageFailure(packages, failureReason);
+ watchdog.notifyPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
if (Flags.recoverabilityDetection()) {
@@ -1746,7 +1818,7 @@ public class PackageWatchdogTest {
Handler handler = new Handler(mTestLooper.getLooper());
PackageWatchdog watchdog =
new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
- mConnectivityModuleConnector, mTestClock);
+ mTestClock);
mockCrashRecoveryProperties(watchdog);
// Verify controller is not automatically started
@@ -1758,8 +1830,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;
@@ -1859,8 +1933,8 @@ public class PackageWatchdogTest {
return mImpact;
}
- public boolean execute(VersionedPackage versionedPackage, int failureReason,
- int mitigationCount) {
+ public boolean onExecuteHealthCheckMitigation(VersionedPackage versionedPackage,
+ int failureReason, int mitigationCount) {
mMitigatedPackages.add(versionedPackage.getPackageName());
mMitigationCounts.add(mitigationCount);
mLastFailureReason = failureReason;
@@ -1883,7 +1957,8 @@ public class PackageWatchdogTest {
return mImpact;
}
- public boolean executeBootLoopMitigation(int level) {
+ public boolean onExecuteBootLoopMitigation(int level) {
+ Slog.w("hrm1243", "I'm here " + level);
mMitigatedBootLoop = true;
mBootMitigationCounts.add(level);
return true;
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 314e95229d29..a6aa877c9097 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -82,7 +82,8 @@ public class NetworkStagedRollbackTest {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.FORCE_STOP_PACKAGES,
- Manifest.permission.WRITE_DEVICE_CONFIG);
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
}
/**
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 4cddcfeb91dc..32deb2e8fdfc 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -70,7 +70,8 @@ public class StagedRollbackTest {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.TEST_MANAGE_ROLLBACKS,
Manifest.permission.FORCE_STOP_PACKAGES,
- Manifest.permission.WRITE_DEVICE_CONFIG);
+ Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
}
/**
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/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/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 8913e8c1996e..05308464cb9b 100644
--- a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -89,7 +89,7 @@ public class LegacyProtoLogImplTest {
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, () -> {});
+ 1024 * 1024, mReader, 1024, (instance) -> {});
}
@After
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
index 6f3deab1d4fa..ed256e72b415 100644
--- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -40,7 +41,7 @@ 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 android.tracing.perfetto.DataSourceParams;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -58,6 +59,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -74,7 +76,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@SuppressWarnings("ConstantConditions")
@Presubmit
@RunWith(JUnit4.class)
-public class PerfettoProtoLogImplTest {
+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()
@@ -94,14 +96,14 @@ public class PerfettoProtoLogImplTest {
);
private static ProtoLogConfigurationService sProtoLogConfigurationService;
+ private static ProtoLogDataSource sTestDataSource;
private static PerfettoProtoLogImpl sProtoLog;
private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder;
- private static Runnable sCacheUpdater;
+ private static ProtoLogCacheUpdater sCacheUpdater;
private static ProtoLogViewerConfigReader sReader;
- public PerfettoProtoLogImplTest() throws IOException {
- }
+ public ProcessedPerfettoProtoLogImplTest() throws IOException { }
@BeforeClass
public static void setUp() throws Exception {
@@ -151,37 +153,38 @@ public class PerfettoProtoLogImplTest {
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock(
ViewerConfigInputStreamProvider.class);
Mockito.when(viewerConfigInputStreamProvider.getInputStream())
- .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()));
+ .thenAnswer(it -> new AutoClosableProtoInputStream(
+ sViewerConfigBuilder.build().toByteArray()));
- sCacheUpdater = () -> {};
+ sCacheUpdater = (instance) -> {};
sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+ sTestDataSource = new ProtoLogDataSource(TEST_PROTOLOG_DATASOURCE_NAME);
+ DataSourceParams params =
+ new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(
+ DataSourceParams
+ .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
+ .build();
+ sTestDataSource.register(params);
+ busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME);
- 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 ProtoInputStream(sViewerConfigBuilder.build().toByteArray());
+ return new AutoClosableProtoInputStream(sViewerConfigBuilder.build().toByteArray());
});
};
sProtoLogConfigurationService =
- new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer);
-
- if (android.tracing.Flags.clientSideProtoLogging()) {
- sProtoLog = new PerfettoProtoLogImpl(
- MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(),
- TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
- } else {
- sProtoLog = new PerfettoProtoLogImpl(
- viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
- TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
- }
+ new ProtoLogConfigurationServiceImpl(sTestDataSource, tracer);
- busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME);
+ sProtoLog = new ProcessedPerfettoProtoLogImpl(sTestDataSource,
+ MOCK_VIEWER_CONFIG_FILE, viewerConfigInputStreamProvider, sReader,
+ (instance) -> sCacheUpdater.update(instance), TestProtoLogGroup.values(),
+ sProtoLogConfigurationService);
+ sProtoLog.enable();
}
@Before
@@ -398,18 +401,17 @@ public class PerfettoProtoLogImplTest {
}
@Test
- public void log_logcatEnabledNoMessage() {
+ 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,
- new Object[]{5});
-
- verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)"));
- verify(sReader).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
@@ -539,16 +541,12 @@ public class PerfettoProtoLogImplTest {
PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder()
.enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME)
.build();
- long before;
- long after;
try {
traceMonitor.start();
- before = SystemClock.elapsedRealtimeNanos();
sProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
0b01100100,
new Object[]{"test", 1, 0.1, true});
- after = SystemClock.elapsedRealtimeNanos();
} finally {
traceMonitor.stop(mWriter);
}
@@ -606,14 +604,15 @@ 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);
- sCacheUpdater = cacheUpdateCallCount::incrementAndGet;
+ sCacheUpdater = (instance) -> cacheUpdateCallCount.incrementAndGet();
PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder()
.enableProtoLog(true,
@@ -867,6 +866,39 @@ public class PerfettoProtoLogImplTest {
.isEqualTo("This message should also be logged 567");
}
+ @Test
+ public void enablesLogGroupAfterLoadingConfig() {
+ sProtoLog.stopLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+
+ doAnswer((Answer<Void>) invocation -> {
+ // logToLogcat is still false before we laod the viewer config
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ return null;
+ }).when(sReader).unloadViewerConfig(any(), any());
+
+ sProtoLog.startLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+ }
+
+ @Test
+ public void disablesLogGroupBeforeUnloadingConfig() {
+ sProtoLog.startLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue();
+
+ doAnswer((Answer<Void>) invocation -> {
+ // Already set logToLogcat to false by the time we unload the config
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ return null;
+ }).when(sReader).unloadViewerConfig(any(), any());
+ sProtoLog.stopLoggingToLogcat(
+ new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {});
+ Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse();
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
index 9d56a92fad52..3d1e208189b0 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import static org.junit.Assert.assertThrows;
+
import android.platform.test.annotations.Presubmit;
import com.android.internal.protolog.common.IProtoLogGroup;
@@ -44,8 +46,29 @@ public class ProtoLogTest {
.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;
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index 28d7b42764c4..9e029a8d5e57 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -19,9 +19,12 @@ 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 android.util.proto.ProtoInputStream;
+import com.google.common.truth.Truth;
+
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -29,6 +32,8 @@ import org.junit.runners.JUnit4;
import perfetto.protos.ProtologCommon;
+import java.io.File;
+
@Presubmit
@RunWith(JUnit4.class)
public class ProtoLogViewerConfigReaderTest {
@@ -83,7 +88,7 @@ public class ProtoLogViewerConfigReaderTest {
).build().toByteArray();
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
- () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+ () -> new AutoClosableProtoInputStream(TEST_VIEWER_CONFIG);
private ProtoLogViewerConfigReader mConfig;
@@ -121,4 +126,37 @@ public class ProtoLogViewerConfigReaderTest {
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 ce519b7a1576..49249333b72b 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -67,9 +67,6 @@ public class ProtologDataSourceTest {
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds =
- new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {});
-
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
ProtologConfig.ProtoLogConfig.newBuilder()
@@ -154,8 +151,7 @@ public class ProtologDataSourceTest {
private ProtoLogDataSource.TlsState createTlsState(
DataSourceConfigOuterClass.DataSourceConfig config) {
- final ProtoLogDataSource ds =
- Mockito.spy(new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}));
+ final ProtoLogDataSource ds = Mockito.spy(new ProtoLogDataSource());
ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
final ProtoLogDataSource.Instance dsInstance = Mockito.spy(
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index e2099e652c49..635e5de935c7 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -18,19 +18,27 @@ package com.android.server.usblib;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.flags.Flags;
import android.os.Binder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -43,13 +51,36 @@ public class UsbManagerTestLib {
private UsbManager mUsbManagerSys;
private UsbManager mUsbManagerMock;
- @Mock private android.hardware.usb.IUsbManager mMockUsbService;
+ @Mock
+ private android.hardware.usb.IUsbManager mMockUsbService;
+ private TestParcelFileDescriptor mTestParcelFileDescriptor = new TestParcelFileDescriptor(
+ new ParcelFileDescriptor(new FileDescriptor()));
+ @Mock
+ private UsbAccessory mMockUsbAccessory;
/**
* Counter for tracking UsbOperation operations.
*/
private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+ private class TestParcelFileDescriptor extends ParcelFileDescriptor {
+
+ private final AtomicInteger mCloseCount = new AtomicInteger();
+
+ TestParcelFileDescriptor(ParcelFileDescriptor wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public void close() {
+ int unused = mCloseCount.incrementAndGet();
+ }
+
+ public void clearCloseCount() {
+ mCloseCount.set(0);
+ }
+ }
+
public UsbManagerTestLib(Context context) {
MockitoAnnotations.initMocks(this);
mContext = context;
@@ -74,6 +105,34 @@ public class UsbManagerTestLib {
mUsbManagerSys.setCurrentFunctions(functions);
}
+ private InputStream openAccessoryInputStream(UsbAccessory accessory) {
+ try {
+ when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor);
+ } catch (RemoteException remEx) {
+ Log.w(TAG, "RemoteException");
+ }
+
+ if (Flags.enableAccessoryStreamApi()) {
+ return mUsbManagerMock.openAccessoryInputStream(accessory);
+ }
+
+ throw new UnsupportedOperationException("Stream APIs not available");
+ }
+
+ private OutputStream openAccessoryOutputStream(UsbAccessory accessory) {
+ try {
+ when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor);
+ } catch (RemoteException remEx) {
+ Log.w(TAG, "RemoteException");
+ }
+
+ if (Flags.enableAccessoryStreamApi()) {
+ return mUsbManagerMock.openAccessoryOutputStream(accessory);
+ }
+
+ throw new UnsupportedOperationException("Stream APIs not available");
+ }
+
private void testSetGetCurrentFunctions_Matched(long functions) {
setCurrentFunctions(functions);
assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions());
@@ -94,7 +153,7 @@ public class UsbManagerTestLib {
try {
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId));
} catch (RemoteException remEx) {
Log.w(TAG, "RemoteException");
}
@@ -118,7 +177,7 @@ public class UsbManagerTestLib {
int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId));
}
public void testGetCurrentFunctions_shouldMatched() {
@@ -138,4 +197,47 @@ public class UsbManagerTestLib {
testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS);
testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NCM);
}
+
+ public void testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed() {
+ mTestParcelFileDescriptor.clearCloseCount();
+ try {
+ try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) {
+ //noinspection EmptyTryBlock
+ try (OutputStream ignored2 = openAccessoryOutputStream(mMockUsbAccessory)) {
+ // do nothing
+ }
+ }
+
+ // ParcelFileDescriptor is closed only once.
+ assertEquals(mTestParcelFileDescriptor.mCloseCount.get(), 1);
+ mTestParcelFileDescriptor.clearCloseCount();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ public void testOnlyOneOpenInputStreamAllowed() {
+ try {
+ //noinspection EmptyTryBlock
+ try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) {
+ assertThrows(IllegalStateException.class,
+ () -> openAccessoryInputStream(mMockUsbAccessory));
+ }
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ public void testOnlyOneOpenOutputStreamAllowed() {
+ try {
+ //noinspection EmptyTryBlock
+ try (OutputStream ignored = openAccessoryOutputStream(mMockUsbAccessory)) {
+ assertThrows(IllegalStateException.class,
+ () -> openAccessoryOutputStream(mMockUsbAccessory));
+ }
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
}
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
index 8b21763b4a24..40fd0b431451 100644
--- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
@@ -18,17 +18,21 @@ package com.android.server.usbtest;
import android.content.Context;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.flags.Flags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Ignore;
+import com.android.server.usblib.UsbManagerTestLib;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import com.android.server.usblib.UsbManagerTestLib;
-
/**
* Unit tests for {@link android.hardware.usb.UsbManager}.
* Note: MUST claimed MANAGE_USB permission in Manifest
@@ -41,6 +45,9 @@ public class UsbManagerApiTest {
private final UsbManagerTestLib mUsbManagerTestLib =
new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext());
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
/**
* Verify NO SecurityException
* Go through System Server
@@ -92,4 +99,23 @@ public class UsbManagerApiTest {
public void testUsbApi_SetCurrentFunctions_shouldMatched() {
mUsbManagerTestLib.testSetCurrentFunctions_shouldMatched();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ public void testUsbApi_closesParcelFileDescriptorAfterAllStreamsClosed() {
+ mUsbManagerTestLib.testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ public void testUsbApi_callingOpenAccessoryInputStreamTwiceThrowsException() {
+ mUsbManagerTestLib.testOnlyOneOpenInputStreamAllowed();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ public void testUsbApi_callingOpenAccessoryOutputStreamTwiceThrowsException() {
+ mUsbManagerTestLib.testOnlyOneOpenOutputStreamAllowed();
+ }
+
}
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
deleted file mode 100644
index d2e1f815e8dc..000000000000
--- a/tests/broadcasts/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 316181
-include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
index 47166a713580..9e15ac41d84b 100644
--- a/tests/broadcasts/unit/Android.bp
+++ b/tests/broadcasts/unit/Android.bp
@@ -1,4 +1,3 @@
-//
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,7 +12,6 @@
// 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
@@ -38,6 +36,7 @@ android_test {
"truth",
"flag-junit",
"android.app.flags-aconfig-java",
+ "junit-params",
],
certificate: "platform",
platform_apis: true,
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
index e9c5248e4d98..61eb230f7957 100644
--- a/tests/broadcasts/unit/AndroidManifest.xml
+++ b/tests/broadcasts/unit/AndroidManifest.xml
@@ -22,6 +22,6 @@
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.broadcasts.unit"
- android:label="Broadcasts Unit Tests"/>
+ android:targetPackage="com.android.broadcasts.unit"
+ android:label="Broadcasts Unit Tests"/>
</manifest> \ No newline at end of file
diff --git a/tests/broadcasts/unit/OWNERS b/tests/broadcasts/unit/OWNERS
new file mode 100644
index 000000000000..f1e450b7e5f9
--- /dev/null
+++ b/tests/broadcasts/unit/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316181
+include platform/frameworks/base:/BROADCASTS_OWNERS \ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
index 0e824c54e92e..b920e2586c86 100644
--- a/tests/broadcasts/unit/TEST_MAPPING
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -1,15 +1,7 @@
{
"postsubmit": [
{
- "name": "BroadcastUnitTests",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
+ "name": "BroadcastUnitTests"
}
]
}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
index b7c412dea999..15a580c9e8f7 100644
--- a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
+++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
@@ -13,246 +13,236 @@
* 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;
+package android.app;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyLong;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.Bundle;
-import android.os.SystemProperties;
+import android.media.AudioManager;
+import android.os.IpcDataCache;
+import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
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.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.annotations.Keep;
import com.android.modules.utils.testing.ExtendedMockitoRule;
-import org.junit.After;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
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;
+import org.mockito.Mock;
-@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
-@RunWith(AndroidJUnit4.class)
-@SmallTest
+@RunWith(JUnitParamsRunner.class)
public class BroadcastStickyCacheTest {
- @ClassRule
- public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
- @Rule
- public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .mockStatic(SystemProperties.class)
+ .mockStatic(IpcDataCache.class)
+ .mockStatic(ActivityManager.class)
.build();
- private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
- ACTION_BATTERY_CHANGED);
+ @Mock
+ private IActivityManager mActivityManagerMock;
- private final TestSystemProps mTestSystemProps = new TestSystemProps();
+ @Mock
+ private IApplicationThread mIApplicationThreadMock;
+
+ @Keep
+ private static Object stickyBroadcastList() {
+ return BroadcastStickyCache.STICKY_BROADCAST_ACTIONS;
+ }
@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()));
- }
+ BroadcastStickyCache.clearCacheForTest();
- @After
- public void tearDown() {
- mTestSystemProps.clear();
- BroadcastStickyCache.clearForTest();
+ doNothing().when(() -> IpcDataCache.invalidateCache(anyString(), anyString()));
}
@Test
- public void testUseCache_nullFilter() {
- assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
+ @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_flagDisabled_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
}
@Test
- public void testUseCache_noActions() {
- final IntentFilter filter = new IntentFilter();
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_nullFilter_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(null));
}
@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);
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_filterWithoutAction_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(new IntentFilter()));
}
@Test
- public void testUseCache_valueNotSet() {
- final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
- assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void useCache_filterWithoutStickyBroadcastAction_returnsFalse() {
+ assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BOOT_COMPLETED)));
}
@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);
- }
+ @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void invalidateCache_flagDisabled_cacheNotInvalidated() {
+ final String apiName = BroadcastStickyCache.sActionApiNameMap.get(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
- @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);
- }
+ BroadcastStickyCache.invalidateCache(
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
- @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);
+ ExtendedMockito.verify(
+ () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)),
+ times(0));
}
@Test
- public void testIncrementVersion_propExists() {
- SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void invalidateCache_broadcastNotSticky_cacheNotInvalidated() {
+ BroadcastStickyCache.invalidateCache(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- 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);
+ ExtendedMockito.verify(
+ () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), anyString()),
+ times(0));
}
@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);
- }
+ @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+ public void invalidateCache_withStickyBroadcast_cacheInvalidated() {
+ final String apiName = BroadcastStickyCache.sActionApiNameMap.get(
+ Intent.ACTION_BATTERY_CHANGED);
- @Test
- public void testIncrementVersionIfExists_propExists() {
- BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.invalidateCache(Intent.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);
+ ExtendedMockito.verify(
+ () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)),
+ times(1));
}
@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);
- }
+ public void invalidateAllCaches_cacheInvalidated() {
+ BroadcastStickyCache.invalidateAllCaches();
- private void assertEquals(Intent actualIntent, Intent expectedIntent) {
- assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
- assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
+ for (int i = BroadcastStickyCache.sActionApiNameMap.size() - 1; i > -1; i--) {
+ final String apiName = BroadcastStickyCache.sActionApiNameMap.valueAt(i);
+ ExtendedMockito.verify(() -> IpcDataCache.invalidateCache(anyString(),
+ eq(apiName)), times(1));
+ }
}
- private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
- assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
- .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
- }
+ @Test
+ @Parameters(method = "stickyBroadcastList")
+ public void getIntent_createNewCache_verifyRegisterReceiverIsCalled(String action)
+ throws RemoteException {
+ setActivityManagerMock(action);
+ final IntentFilter filter = new IntentFilter(action);
+ final Intent intent = queryIntent(filter);
- private static final class TestSystemProps {
- @GuardedBy("mSysProps")
- private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
+ assertNotNull(intent);
+ assertEquals(intent.getAction(), action);
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ eq(filter), anyString(), anyInt(), anyInt());
+ }
- public void add(String name, long value) {
- synchronized (mSysProps) {
- mSysProps.put(name, value);
- }
- }
+ @Test
+ public void getIntent_querySameValueTwice_verifyRegisterReceiverIsCalledOnce()
+ throws RemoteException {
+ setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW);
+ final Intent intent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+ final Intent cachedIntent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
- public long get(String name, long defaultValue) {
- synchronized (mSysProps) {
- final int idx = mSysProps.indexOfKey(name);
- return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
- }
- }
+ assertNotNull(intent);
+ assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+ assertNotNull(cachedIntent);
+ assertEquals(cachedIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
- public Handle query(String name) {
- synchronized (mSysProps) {
- return mSysProps.containsKey(name) ? new Handle(name) : null;
- }
- }
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ any(), anyString(), anyInt(), anyInt());
+ }
- public void clear() {
- synchronized (mSysProps) {
- mSysProps.clear();
- }
- }
+ @Test
+ public void getIntent_queryActionTwiceWithNullResult_verifyRegisterReceiverIsCalledOnce()
+ throws RemoteException {
+ setActivityManagerMock(null);
+ final Intent intent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_FULL));
+ final Intent cachedIntent = queryIntent(
+ new IntentFilter(Intent.ACTION_DEVICE_STORAGE_FULL));
- public class Handle {
- private final String mName;
+ assertNull(intent);
+ assertNull(cachedIntent);
- Handle(String name) {
- mName = name;
- }
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ any(), anyString(), anyInt(), anyInt());
+ }
- public long getLong(long defaultValue) {
- return get(mName, defaultValue);
- }
- }
+ @Test
+ public void getIntent_querySameActionWithDifferentFilter_verifyRegisterReceiverCalledTwice()
+ throws RemoteException {
+ setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW);
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+ final Intent intent = queryIntent(filter);
+
+ final IntentFilter newFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+ newFilter.addDataScheme("file");
+ final Intent newIntent = queryIntent(newFilter);
+
+ assertNotNull(intent);
+ assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+ assertNotNull(newIntent);
+ assertEquals(newIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW);
+
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ eq(filter), anyString(), anyInt(), anyInt());
+
+ verify(mActivityManagerMock, times(1)).registerReceiverWithFeature(
+ eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(),
+ eq(newFilter), anyString(), anyInt(), anyInt());
+ }
+
+ private Intent queryIntent(IntentFilter filter) {
+ return BroadcastStickyCache.getIntent(
+ mIApplicationThreadMock,
+ "android",
+ "android",
+ filter,
+ "system",
+ 0,
+ 0
+ );
+ }
+
+ private void setActivityManagerMock(String action) throws RemoteException {
+ when(ActivityManager.getService()).thenReturn(mActivityManagerMock);
+ when(mActivityManagerMock.registerReceiverWithFeature(any(), anyString(),
+ anyString(), anyString(), any(), any(), anyString(), anyInt(),
+ anyInt())).thenReturn(action != null ? new Intent(action) : null);
}
}
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/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml
index 25092b52e2b6..c293589bdbaf 100644
--- a/tests/graphics/SilkFX/AndroidManifest.xml
+++ b/tests/graphics/SilkFX/AndroidManifest.xml
@@ -23,13 +23,12 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application android:label="SilkFX"
- android:theme="@style/Theme.UsefulDefault">
+ android:theme="@android:style/Theme.Material">
<activity android:name=".Main"
android:label="SilkFX Demos"
android:banner="@drawable/background1"
- android:exported="true"
- android:theme="@style/Theme.UsefulDefault">
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
index f13c0883cb01..27eca82dcb23 100644
--- a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
+++ b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml
@@ -13,161 +13,168 @@
~ 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"
+-->
+<FrameLayout
+ 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:id="@+id/background"
- android:layout_width="390dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:padding="15dp"
- android:orientation="vertical"
+ android:fitsSystemWindows="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
tools:context=".materials.BackgroundBlurActivity">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:padding="10dp"
- android:textColor="#ffffffff"
- android:text="Hello blurry world!"/>
-
<LinearLayout
- android:layout_width="match_parent"
+ android:id="@+id/background"
+ android:layout_width="390dp"
android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textColor="#ffffffff"
- android:text="Background blur"/>
+ android:layout_gravity="center"
+ android:padding="15dp"
+ android:orientation="vertical">
- <SeekBar
- android:id="@+id/set_background_blur"
- android:min="0"
- android:max="300"
- android:layout_width="160dp"
- android:layout_height="wrap_content"/>
- <TextView
- android:id="@+id/background_blur_radius"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#ffffffff"
- android:ems="3"
- android:gravity="center"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:text="TODO"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
<TextView
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
+ android:gravity="center_horizontal"
+ android:padding="10dp"
android:textColor="#ffffffff"
- android:text="Background alpha"/>
+ android:text="Hello blurry world!"/>
- <SeekBar
- android:id="@+id/set_background_alpha"
- android:min="0"
- android:max="100"
- android:layout_width="160dp"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/background_alpha"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="#ffffffff"
- android:ems="3"
- android:gravity="center"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:text="TODO"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textColor="#ffffffff"
- android:text="Blur behind"/>
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Background blur"/>
- <SeekBar
- android:id="@+id/set_blur_behind"
- android:min="0"
- android:max="300"
- android:layout_width="160dp"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/blur_behind_radius"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="#ffffffff"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:ems="3"
- android:text="TODO"/>
- </LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:layout_width="wrap_content"
+ <SeekBar
+ android:id="@+id/set_background_blur"
+ android:min="0"
+ android:max="300"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/background_blur_radius"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ffffffff"
+ android:ems="3"
+ android:gravity="center"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:text="TODO"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textColor="#ffffffff"
- android:text="Dim amount"/>
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Background alpha"/>
- <SeekBar
- android:id="@+id/set_dim_amount"
- android:min="0"
- android:max="100"
- android:layout_width="160dp"
- android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/dim_amount"
- android:layout_width="wrap_content"
+ <SeekBar
+ android:id="@+id/set_background_alpha"
+ android:min="0"
+ android:max="100"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/background_alpha"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ffffffff"
+ android:ems="3"
+ android:gravity="center"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:text="TODO"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="#ffffffff"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:ems="3"
- android:text="TODO"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginTop="5dp"
- android:orientation="vertical"
- android:gravity="center">
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Blur behind"/>
- <Button
- android:id="@+id/toggle_blur_enabled"
+ <SeekBar
+ android:id="@+id/set_blur_behind"
+ android:min="0"
+ android:max="300"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/blur_behind_radius"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="#ffffffff"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:ems="3"
+ android:text="TODO"/>
+ </LinearLayout>
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="Disable blur"
- android:onClick="toggleForceBlurDisabled"/>
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textColor="#ffffffff"
+ android:text="Dim amount"/>
- <Button
- android:id="@+id/toggle_battery_saving_mode"
+ <SeekBar
+ android:id="@+id/set_dim_amount"
+ android:min="0"
+ android:max="100"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/dim_amount"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="#ffffffff"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:ems="3"
+ android:text="TODO"/>
+ </LinearLayout>
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="TODO"
- android:onClick="toggleBatterySavingMode"/>
- </LinearLayout>
- <requestFocus/>
+ android:layout_gravity="center"
+ android:layout_marginTop="5dp"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <Button
+ android:id="@+id/toggle_blur_enabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Disable blur"
+ android:onClick="toggleForceBlurDisabled"/>
-</LinearLayout>
+ <Button
+ android:id="@+id/toggle_battery_saving_mode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="TODO"
+ android:onClick="toggleBatterySavingMode"/>
+ </LinearLayout>
+ <requestFocus/>
+
+ </LinearLayout>
+</FrameLayout>
diff --git a/tests/graphics/SilkFX/res/layout/activity_glass.xml b/tests/graphics/SilkFX/res/layout/activity_glass.xml
index aa09f276d5c8..d591fc4606b0 100644
--- a/tests/graphics/SilkFX/res/layout/activity_glass.xml
+++ b/tests/graphics/SilkFX/res/layout/activity_glass.xml
@@ -19,6 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
tools:context=".MainActivity">
<ImageView
@@ -300,4 +301,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
-</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
index c0c0bab8a605..9b2b0c818a8e 100644
--- a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
+++ b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml
@@ -19,6 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_margin="8dp"
android:orientation="vertical">
<TextView
@@ -61,4 +62,4 @@
</LinearLayout>
-</com.android.test.silkfx.common.ColorModeControls> \ No newline at end of file
+</com.android.test.silkfx.common.ColorModeControls>
diff --git a/tests/graphics/SilkFX/res/layout/common_base.xml b/tests/graphics/SilkFX/res/layout/common_base.xml
index c0eaf9bc1476..ce6d850af1bc 100644
--- a/tests/graphics/SilkFX/res/layout/common_base.xml
+++ b/tests/graphics/SilkFX/res/layout/common_base.xml
@@ -18,6 +18,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical">
<include layout="@layout/color_mode_controls" />
@@ -26,4 +27,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/tests/graphics/SilkFX/res/layout/hdr_glows.xml b/tests/graphics/SilkFX/res/layout/hdr_glows.xml
index b6050645866a..f1e553a3df23 100644
--- a/tests/graphics/SilkFX/res/layout/hdr_glows.xml
+++ b/tests/graphics/SilkFX/res/layout/hdr_glows.xml
@@ -18,6 +18,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical">
<include layout="@layout/color_mode_controls" />
@@ -48,4 +49,4 @@
android:layout_height="50dp"
android:layout_margin="8dp" />
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
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/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml
index 4dd626dfb8f5..75506978024b 100644
--- a/tests/graphics/SilkFX/res/values/style.xml
+++ b/tests/graphics/SilkFX/res/values/style.xml
@@ -16,21 +16,16 @@
-->
<!-- Styles for immersive actions UI. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.BackgroundBlurTheme" parent= "Theme.AppCompat.Dialog">
+ <style name="Theme.BackgroundBlurTheme" parent="Theme.AppCompat.Dialog">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBlurBehindEnabled">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowElevation">0dp</item>
<item name="buttonStyle">@style/AppTheme.Button</item>
<item name="colorAccent">#bbffffff</item>
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="AppTheme.Button" parent="Widget.AppCompat.Button">
<item name="android:textColor">#ffffffff</item>
</style>
- <style name="Theme.UsefulDefault" parent="android:Theme.Material">
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
- </style>
-
</resources>
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..ad7cde44bb35 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)
))
)
@@ -71,6 +72,7 @@ class Main : Activity() {
super.onCreate(savedInstanceState)
val list = ExpandableListView(this)
+ list.setFitsSystemWindows(true)
setContentView(list)
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/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index 07b733830bd3..0da4521fca71 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -143,6 +143,38 @@ public class VibratorManagerServicePermissionTest {
}
@Test
+ public void testStartVendorVibrationSessionWithoutVibratePermissionFails() throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+ Manifest.permission.START_VIBRATION_SESSIONS);
+ expectSecurityException("VIBRATE");
+ mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+ new int[] { 1 }, ATTRS, "testVibrate", null);
+ }
+
+ @Test
+ public void testStartVendorVibrationSessionWithoutVibrateVendorEffectsPermissionFails()
+ throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.VIBRATE,
+ Manifest.permission.START_VIBRATION_SESSIONS);
+ expectSecurityException("VIBRATE");
+ mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+ new int[] { 1 }, ATTRS, "testVibrate", null);
+ }
+
+ @Test
+ public void testStartVendorVibrationSessionWithoutStartSessionPermissionFails()
+ throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.VIBRATE,
+ Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+ expectSecurityException("VIBRATE");
+ mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+ new int[] { 1 }, ATTRS, "testVibrate", null);
+ }
+
+ @Test
public void testCancelVibrateFails() throws RemoteException {
expectSecurityException("VIBRATE");
mVibratorService.cancelVibrate(/* usageFilter= */ -1, new Binder());
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/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/tests/Android.bp b/tests/testables/tests/Android.bp
index 1eb36fa5f908..f0cda535b3aa 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -29,13 +29,19 @@ android_test {
"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",
],
@@ -50,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/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..56b0a25ed2dd 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -93,8 +93,8 @@ 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);