summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp37
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java2
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java138
-rw-r--r--api/ApiDocs.bp1
-rw-r--r--api/StubLibraries.bp8
-rw-r--r--api/javadoc-lint-baseline121
-rw-r--r--core/api/current.txt96
-rw-r--r--core/api/module-lib-current.txt2
-rw-r--r--core/api/module-lib-removed.txt2
-rw-r--r--core/api/removed.txt2
-rw-r--r--core/api/system-current.txt55
-rw-r--r--core/api/system-removed.txt2
-rw-r--r--core/api/test-current.txt15
-rw-r--r--core/api/test-removed.txt2
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java15
-rw-r--r--core/java/android/app/ActivityOptions.java6
-rw-r--r--core/java/android/app/AppOpsManager.java29
-rw-r--r--core/java/android/app/BroadcastOptions.java3
-rw-r--r--core/java/android/app/LoadedApk.java11
-rw-r--r--core/java/android/app/Notification.java8
-rw-r--r--core/java/android/app/assist/AssistStructure.java25
-rw-r--r--core/java/android/app/servertransaction/WindowStateResizeItem.java193
-rw-r--r--core/java/android/app/usage/flags.aconfig6
-rw-r--r--core/java/android/companion/CompanionDeviceService.java12
-rw-r--r--core/java/android/companion/virtual/VirtualDevice.java5
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java6
-rw-r--r--core/java/android/content/AttributionSource.java3
-rw-r--r--core/java/android/content/Context.java5
-rw-r--r--core/java/android/content/Intent.java9
-rw-r--r--core/java/android/content/pm/PackageManager.java17
-rw-r--r--core/java/android/credentials/CreateCredentialException.java8
-rw-r--r--core/java/android/credentials/CredentialDescription.java6
-rw-r--r--core/java/android/credentials/CredentialProviderInfo.java5
-rw-r--r--core/java/android/credentials/GetCredentialException.java8
-rw-r--r--core/java/android/credentials/PrepareGetCredentialResponse.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteRawStatement.java2
-rw-r--r--core/java/android/database/sqlite/flags.aconfig9
-rw-r--r--core/java/android/hardware/display/BrightnessInfo.java10
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java90
-rw-r--r--core/java/android/hardware/input/InputManager.java2
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java2
-rw-r--r--core/java/android/os/BatteryStats.java2
-rw-r--r--core/java/android/os/BatteryUsageStatsQuery.java1
-rw-r--r--core/java/android/os/BugreportManager.java5
-rw-r--r--core/java/android/os/PowerManager.java2
-rw-r--r--core/java/android/os/PowerMonitor.java61
-rw-r--r--core/java/android/os/PowerMonitorReadings.java13
-rw-r--r--core/java/android/os/RemoteException.java2
-rw-r--r--core/java/android/os/flags.aconfig2
-rw-r--r--core/java/android/os/health/SystemHealthManager.java70
-rw-r--r--core/java/android/os/vibrator/flags.aconfig10
-rw-r--r--core/java/android/permission/flags.aconfig7
-rw-r--r--core/java/android/provider/Settings.java9
-rw-r--r--core/java/android/service/autofill/FillResponse.java2
-rw-r--r--core/java/android/service/autofill/SaveInfo.java2
-rw-r--r--core/java/android/service/credentials/BeginCreateCredentialResponse.java2
-rw-r--r--core/java/android/service/credentials/BeginGetCredentialResponse.java2
-rw-r--r--core/java/android/service/credentials/CallingAppInfo.java2
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java11
-rw-r--r--core/java/android/service/quickaccesswallet/WalletCard.java6
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java2
-rw-r--r--core/java/android/text/DynamicLayout.java2
-rw-r--r--core/java/android/text/WordSegmentFinder.java2
-rw-r--r--core/java/android/view/Display.java4
-rw-r--r--core/java/android/view/IWindow.aidl4
-rw-r--r--core/java/android/view/InputDevice.java3
-rw-r--r--core/java/android/view/ScrollFeedbackProvider.java4
-rw-r--r--core/java/android/view/SurfaceControl.java20
-rw-r--r--core/java/android/view/View.java1
-rw-r--r--core/java/android/view/ViewRootImpl.java5
-rw-r--r--core/java/android/view/ViewStructure.java7
-rw-r--r--core/java/android/view/WindowManager.java13
-rw-r--r--core/java/android/view/WindowMetrics.java11
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java8
-rw-r--r--core/java/android/view/accessibility/AccessibilityWindowInfo.java1
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig7
-rw-r--r--core/java/android/view/animation/AnimationUtils.java2
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java3
-rw-r--r--core/java/android/view/contentprotection/flags/content_protection_flags.aconfig7
-rw-r--r--core/java/android/view/displayhash/DisplayHashResultCallback.java2
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig7
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java7
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java17
-rw-r--r--core/java/android/view/inspector/PropertyReader.java2
-rw-r--r--core/java/android/window/BackEvent.java2
-rw-r--r--core/java/android/window/SurfaceSyncGroup.java1
-rw-r--r--core/java/android/window/SystemPerformanceHinter.java12
-rw-r--r--core/java/android/window/TransitionInfo.java10
-rw-r--r--core/java/android/window/WindowProviderService.java2
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig7
-rw-r--r--core/java/com/android/internal/content/FileSystemProvider.java168
-rw-r--r--core/java/com/android/internal/jank/DisplayResolutionTracker.java4
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java123
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistoryIterator.java25
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java25
-rw-r--r--core/java/com/android/internal/os/MonotonicClock.java145
-rw-r--r--core/java/com/android/internal/os/MultiStateStats.java139
-rw-r--r--core/java/com/android/internal/os/OWNERS8
-rw-r--r--core/java/com/android/internal/os/PowerStats.java87
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogGroup.java2
-rw-r--r--core/jni/AndroidRuntime.cpp9
-rw-r--r--core/jni/android_view_SurfaceControl.cpp7
-rw-r--r--core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp11
-rw-r--r--core/res/AndroidManifest.xml16
-rw-r--r--core/res/res/values/config_battery_stats.xml9
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java80
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java4
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java21
-rw-r--r--core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java126
-rw-r--r--core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java41
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java1
-rw-r--r--core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java58
-rw-r--r--data/etc/services.core.protolog.json21
-rw-r--r--graphics/java/Android.bp6
-rw-r--r--graphics/java/android/framework_graphics.aconfig8
-rw-r--r--graphics/java/android/graphics/Gainmap.java4
-rw-r--r--graphics/java/android/graphics/Paint.java2
-rw-r--r--graphics/java/android/graphics/Path.java4
-rw-r--r--graphics/java/android/graphics/drawable/RippleShader.java5
-rw-r--r--graphics/java/android/graphics/text/LineBreaker.java2
-rw-r--r--graphics/java/android/view/PixelCopy.java2
-rw-r--r--keystore/java/android/security/AndroidKeyStoreMaintenance.java19
-rw-r--r--keystore/java/android/security/KeyStore.java33
-rw-r--r--keystore/java/android/security/KeyStoreException.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java61
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig7
-rw-r--r--libs/hwui/api/current.txt2
-rw-r--r--libs/hwui/api/module-lib-current.txt2
-rw-r--r--libs/hwui/api/module-lib-removed.txt2
-rw-r--r--libs/hwui/api/removed.txt2
-rw-r--r--libs/hwui/api/system-current.txt2
-rw-r--r--libs/hwui/api/system-removed.txt2
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp3
-rw-r--r--location/java/android/location/GnssSignalType.java5
-rw-r--r--media/java/android/media/AudioFormat.java6
-rw-r--r--media/java/android/media/MediaRouter2.java2
-rw-r--r--media/java/android/media/RingtoneV1.java43
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java3
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--media/java/android/media/midi/MidiUmpDeviceService.java2
-rw-r--r--media/java/android/media/projection/IMediaProjectionManager.aidl20
-rw-r--r--media/tests/MediaFrameworkTest/AndroidManifest.xml1
-rw-r--r--media/tests/MediaFrameworkTest/AndroidTest.xml1
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java6
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java29
-rw-r--r--media/tests/ringtone/Android.bp15
-rw-r--r--media/tests/ringtone/OWNERS3
-rw-r--r--media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java (renamed from media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java)285
-rw-r--r--media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java75
-rw-r--r--media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java225
-rw-r--r--omapi/aidl/Android.bp5
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java173
-rw-r--r--packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java99
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt)4
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml4
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts4
-rw-r--r--packages/SettingsLib/Spa/testutils/build.gradle.kts2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt19
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt8
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt101
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt22
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt13
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt149
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java21
-rw-r--r--packages/SystemUI/Android.bp6
-rw-r--r--packages/SystemUI/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/TEST_MAPPING23
-rw-r--r--packages/SystemUI/aconfig/Android.bp16
-rw-r--r--packages/SystemUI/aconfig/communal.aconfig2
-rw-r--r--packages/SystemUI/communal/layout/tests/Android.bp2
-rw-r--r--packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt2
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt6
-rw-r--r--packages/SystemUI/compose/features/Android.bp2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt53
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt3
-rw-r--r--packages/SystemUI/compose/scene/Android.bp39
-rw-r--r--packages/SystemUI/compose/scene/AndroidManifest.xml22
-rw-r--r--packages/SystemUI/compose/scene/OWNERS13
-rw-r--r--packages/SystemUI/compose/scene/TEST_MAPPING48
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt)84
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt)26
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt)11
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt)0
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt (renamed from packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/Android.bp50
-rw-r--r--packages/SystemUI/compose/scene/tests/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt218
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt)3
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt)1
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt)0
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt (renamed from packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt)0
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java51
-rw-r--r--packages/SystemUI/res-keyguard/drawable/progress_bar.xml45
-rw-r--r--packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml32
-rw-r--r--packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml23
-rw-r--r--packages/SystemUI/res/drawable/mobile_network_type_background.xml30
-rw-r--r--packages/SystemUI/res/layout/connected_display_dialog.xml12
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml3
-rw-r--r--packages/SystemUI/res/values/config.xml14
-rw-r--r--packages/SystemUI/res/values/dimens.xml20
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml20
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt2
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java21
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java21
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt138
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java392
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt80
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt410
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java597
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java298
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt)0
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/accessibility/accessibility.aconfig14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java192
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java18
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java1
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java10
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java35
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java17
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java14
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java144
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java4
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java50
-rw-r--r--services/core/java/com/android/server/biometrics/biometrics.aconfig7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java20
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/LockoutCache.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java319
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java26
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java24
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java410
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java347
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java524
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java115
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java274
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java40
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java24
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java361
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java231
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java445
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java95
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java22
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java5
-rw-r--r--services/core/java/com/android/server/display/DeviceStateToLayoutMap.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java30
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java19
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java26
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java6
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java3
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java53
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java295
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java192
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java13
-rw-r--r--services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java6
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java13
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/layout/Layout.java31
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeTrackerService.java108
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStorage.java2
-rw-r--r--services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java2
-rw-r--r--services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java2
-rw-r--r--services/core/java/com/android/server/media/BluetoothRouteController.java8
-rw-r--r--services/core/java/com/android/server/media/DeviceRouteController.java8
-rw-r--r--services/core/java/com/android/server/media/LegacyBluetoothRouteController.java2
-rw-r--r--services/core/java/com/android/server/media/LegacyDeviceRouteController.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java19
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java10
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java37
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java24
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java50
-rw-r--r--services/core/java/com/android/server/oemlock/OemLockService.java2
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java4
-rw-r--r--services/core/java/com/android/server/pdb/OWNERS2
-rw-r--r--services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java (renamed from services/core/java/com/android/server/PersistentDataBlockManagerInternal.java)2
-rw-r--r--services/core/java/com/android/server/pdb/PersistentDataBlockService.java (renamed from services/core/java/com/android/server/PersistentDataBlockService.java)5
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java22
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java16
-rw-r--r--services/core/java/com/android/server/pm/MovePackageHelper.java21
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java46
-rw-r--r--services/core/java/com/android/server/pm/ResolveIntentHelper.java6
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java6
-rw-r--r--services/core/java/com/android/server/pm/Settings.java21
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java24
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java2
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java22
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java4
-rw-r--r--services/core/java/com/android/server/pm/pkg/SuspendParams.java4
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStats.java172
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java156
-rw-r--r--services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java48
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java68
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java58
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java49
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java338
-rw-r--r--services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java10
-rw-r--r--services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java123
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsAggregator.java178
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsScheduler.java261
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsSpan.java433
-rw-r--r--services/core/java/com/android/server/power/stats/PowerStatsStore.java371
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java10
-rw-r--r--services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java7
-rw-r--r--services/core/java/com/android/server/testharness/TestHarnessModeService.java2
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java10
-rw-r--r--services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java5
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java15
-rw-r--r--services/core/java/com/android/server/wm/AnimationAdapter.java4
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java331
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java3
-rw-r--r--services/core/java/com/android/server/wm/InputConsumerImpl.java6
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java7
-rw-r--r--services/core/java/com/android/server/wm/LegacyDimmer.java341
-rw-r--r--services/core/java/com/android/server/wm/OWNERS2
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/SmoothDimmer.java395
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java6
-rw-r--r--services/core/java/com/android/server/wm/Task.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java56
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerFlags.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java78
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/core/jni/OWNERS1
-rw-r--r--services/core/jni/com_android_server_PersistentDataBlockService.cpp120
-rw-r--r--services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp113
-rw-r--r--services/core/jni/onload.cpp4
-rw-r--r--services/core/jni/tvinput/JTvInputHal.cpp9
-rw-r--r--services/core/jni/tvinput/JTvInputHal.h1
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2
-rw-r--r--services/java/com/android/server/SystemServer.java1
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java42
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java41
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java19
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java9
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java246
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java44
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java283
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java201
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java2
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java22
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java63
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java4
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java227
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java106
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java269
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java179
-rw-r--r--services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java85
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java105
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java187
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java169
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java121
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java88
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java120
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java342
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java92
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java123
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java147
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java105
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java101
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java148
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java71
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/pdb/OWNERS1
-rw-r--r--services/tests/vibrator/Android.bp1
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DimmerTests.java208
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java68
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java64
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java34
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java22
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java2
-rw-r--r--test-mock/api/current.txt21
-rw-r--r--test-mock/api/removed.txt4
-rw-r--r--test-mock/api/system-current.txt46
-rw-r--r--test-mock/api/system-removed.txt2
-rw-r--r--test-mock/api/test-current.txt16
-rw-r--r--test-mock/api/test-removed.txt2
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java2
-rw-r--r--tests/FlickerTests/AndroidTestTemplate.xml2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt16
-rw-r--r--tools/hoststubgen/hoststubgen/Android.bp5
-rw-r--r--tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java4
-rw-r--r--tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java4
-rw-r--r--tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java12
-rwxr-xr-xtools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh8
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java42
-rwxr-xr-xtools/hoststubgen/scripts/run-all-tests.sh3
-rw-r--r--tools/lint/global/integration_tests/Android.bp45
-rw-r--r--tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java28
-rw-r--r--tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl7
-rw-r--r--tools/lint/global/integration_tests/tests.py16
647 files changed, 18808 insertions, 5817 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2d164f891590..36430b6a215d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -33,10 +33,13 @@ aconfig_srcjars = [
":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.audio.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":sdk_sandbox_flags_lib{.generated_srcjars}",
":android.permission.flags-aconfig-java{.generated_srcjars}",
+ ":android.database.sqlite-aconfig-java{.generated_srcjars}",
":hwui_flags_java_lib{.generated_srcjars}",
+ ":framework_graphics_flags_java_lib{.generated_srcjars}",
":display_flags_lib{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":android.multiuser.flags-aconfig-java{.generated_srcjars}",
@@ -45,6 +48,7 @@ aconfig_srcjars = [
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.net.flags-aconfig-java{.generated_srcjars}",
]
filegroup {
@@ -314,6 +318,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media Audio
+java_aconfig_library {
+ name: "com.android.media.audio.flags-aconfig-java",
+ aconfig_declarations: "aconfig_audio_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Permissions
aconfig_declarations {
name: "android.permission.flags-aconfig",
@@ -332,6 +343,19 @@ java_aconfig_library {
}
+// SQLite
+aconfig_declarations {
+ name: "android.database.sqlite-aconfig",
+ package: "android.database.sqlite",
+ srcs: ["core/java/android/database/sqlite/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.database.sqlite-aconfig-java",
+ aconfig_declarations: "android.database.sqlite-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Biometrics
aconfig_declarations {
name: "android.hardware.biometrics.flags-aconfig",
@@ -352,6 +376,12 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "framework_graphics_flags_java_lib",
+ aconfig_declarations: "framework_graphics_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Display
java_aconfig_library {
name: "display_flags_lib",
@@ -471,3 +501,10 @@ java_aconfig_library {
aconfig_declarations: "android.companion.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// CoreNetworking
+java_aconfig_library {
+ name: "com.android.net.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index f1403bd51049..e833bb95a302 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -439,7 +439,7 @@ public class JobParameters implements Parcelable {
* provides an easy way to tell whether the job is being executed due to the deadline
* expiring. Note: If the job is running because its deadline expired, it implies that its
* constraints will not be met. However,
- * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever
+ * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever
* run when their constraints are satisfied, therefore, the constraints will still be satisfied
* for a periodic job even if the deadline has expired.
*/
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 4e3cb7d83451..3e835b8ad429 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -2,7 +2,7 @@ package: "com.android.server.job"
flag {
name: "relax_prefetch_connectivity_constraint_only_on_charger"
- namespace: "backstagepower"
+ namespace: "backstage_power"
description: "Only relax a prefetch job's connectivity constraint when the device is charging"
bug: "299329948"
} \ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 3cbee5d07bb9..384a480af4e9 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -173,8 +173,6 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import dalvik.annotation.optimization.NeverCompile;
-import libcore.util.EmptyArray;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -260,7 +258,7 @@ public class AlarmManagerService extends SystemService {
/**
* A map from uid to the last op-mode we have seen for
* {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change
- * when the denylist changes.
+ * when the app-op changes.
*/
@VisibleForTesting
@GuardedBy("mLock")
@@ -671,9 +669,6 @@ public class AlarmManagerService extends SystemService {
@VisibleForTesting
final class Constants implements DeviceConfig.OnPropertiesChangedListener,
EconomyManagerInternal.TareStateChangeListener {
- @VisibleForTesting
- static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250;
-
// Key names stored in the settings value.
@VisibleForTesting
static final String KEY_MIN_FUTURITY = "min_futurity";
@@ -727,8 +722,6 @@ public class AlarmManagerService extends SystemService {
@VisibleForTesting
static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";
@VisibleForTesting
- static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list";
- @VisibleForTesting
static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz";
@VisibleForTesting
static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz";
@@ -835,13 +828,6 @@ public class AlarmManagerService extends SystemService {
public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;
/**
- * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for
- * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can
- * be accessed without synchronizing on {@link #mLock}.
- */
- public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet();
-
- /**
* Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent
* WAKE_FROM_IDLE alarm.
*/
@@ -1025,21 +1011,6 @@ public class AlarmManagerService extends SystemService {
PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,
DEFAULT_PRIORITY_ALARM_DELAY);
break;
- case KEY_EXACT_ALARM_DENY_LIST:
- final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST,
- "");
- final String[] values = rawValue.isEmpty()
- ? EmptyArray.STRING
- : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1);
- if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) {
- Slog.w(TAG, "Deny list too long, truncating to "
- + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements.");
- updateExactAlarmDenyList(
- Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE));
- } else {
- updateExactAlarmDenyList(values);
- }
- break;
case KEY_MIN_DEVICE_IDLE_FUZZ:
case KEY_MAX_DEVICE_IDLE_FUZZ:
if (!deviceIdleFuzzBoundariesUpdated) {
@@ -1110,28 +1081,6 @@ public class AlarmManagerService extends SystemService {
}
}
- private void updateExactAlarmDenyList(String[] newDenyList) {
- final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList));
- final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST);
- final Set<String> added = new ArraySet<>(newDenyList);
-
- added.removeAll(EXACT_ALARM_DENY_LIST);
- removed.removeAll(newSet);
- if (added.size() > 0) {
- mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added)
- .sendToTarget();
- }
- if (removed.size() > 0) {
- mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed)
- .sendToTarget();
- }
- if (newDenyList.length == 0) {
- EXACT_ALARM_DENY_LIST = Collections.emptySet();
- } else {
- EXACT_ALARM_DENY_LIST = newSet;
- }
- }
-
private void updateDeviceIdleFuzzBoundaries() {
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_ALARM_MANAGER,
@@ -1277,9 +1226,6 @@ public class AlarmManagerService extends SystemService {
TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);
pw.println();
- pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST);
- pw.println();
-
pw.print(KEY_MIN_DEVICE_IDLE_FUZZ);
pw.print("=");
TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw);
@@ -2114,14 +2060,10 @@ public class AlarmManagerService extends SystemService {
? permissionState
: (newMode == AppOpsManager.MODE_ALLOWED);
} else {
- final boolean allowedByDefault =
- !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT)
- ? allowedByDefault
- : (oldMode == AppOpsManager.MODE_ALLOWED);
+ || (oldMode == AppOpsManager.MODE_ALLOWED);
hasPermission = (newMode == AppOpsManager.MODE_DEFAULT)
- ? allowedByDefault
- : (newMode == AppOpsManager.MODE_ALLOWED);
+ || (newMode == AppOpsManager.MODE_ALLOWED);
}
if (hadPermission && !hasPermission) {
@@ -2769,11 +2711,8 @@ public class AlarmManagerService extends SystemService {
// Compatibility permission check for older apps.
final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,
packageName);
- if (mode == AppOpsManager.MODE_DEFAULT) {
- hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);
- } else {
- hasPermission = (mode == AppOpsManager.MODE_ALLOWED);
- }
+ hasPermission = (mode == AppOpsManager.MODE_DEFAULT)
+ || (mode == AppOpsManager.MODE_ALLOWED);
}
mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start);
return hasPermission;
@@ -3993,63 +3932,6 @@ public class AlarmManagerService extends SystemService {
}
/**
- * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that
- * either got added or deleted.
- * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission.
- *
- * Note that these packages don't need to be installed on the device, but if they are and they
- * do undergo a permission change, we will handle them appropriately.
- *
- * This should not be called with the lock held as it calls out to other services.
- * This is not expected to get called frequently.
- */
- void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) {
- Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from")
- + " the exact alarm deny list.");
-
- final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds();
-
- for (int i = 0; i < changedPackages.size(); i++) {
- final String changedPackage = changedPackages.valueAt(i);
- for (final int userId : startedUserIds) {
- final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId);
- if (uid <= 0) {
- continue;
- }
- if (!isExactAlarmChangeEnabled(changedPackage, userId)) {
- continue;
- }
- if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) {
- continue;
- }
- if (hasUseExactAlarmInternal(changedPackage, uid)) {
- continue;
- }
- if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) {
- // Permission isn't requested, deny list doesn't matter.
- continue;
- }
- final int appOpMode;
- synchronized (mLock) {
- appOpMode = mLastOpScheduleExactAlarm.get(uid,
- AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM));
- }
- if (appOpMode != AppOpsManager.MODE_DEFAULT) {
- // Deny list doesn't matter.
- continue;
- }
- // added: true => package was added to the deny list
- // added: false => package was removed from the deny list
- if (added) {
- removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true);
- } else {
- sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId);
- }
- }
- }
- }
-
- /**
* Called when an app loses the permission to use exact alarms. This will happen when the app
* no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or
* {@link Manifest.permission#USE_EXACT_ALARM}.
@@ -4931,8 +4813,8 @@ public class AlarmManagerService extends SystemService {
public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_CANCELED = 7;
public static final int REMOVE_EXACT_ALARMS = 8;
- public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9;
- public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10;
+ // Unused id 9
+ // Unused id 10
public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
public static final int TARE_AFFORDABILITY_CHANGED = 12;
public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
@@ -5041,12 +4923,6 @@ public class AlarmManagerService extends SystemService {
String packageName = (String) msg.obj;
removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true);
break;
- case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED:
- handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true);
- break;
- case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED:
- handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false);
- break;
case REFRESH_EXACT_ALARM_CANDIDATES:
refreshExactAlarmCandidates();
break;
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 30b442336148..7142eb5bef53 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -124,7 +124,6 @@ droidstubs {
"packages/modules/Media/apex/aidl/stable",
],
},
- extensions_info_file: ":sdk-extensions-info",
}
droidstubs {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index fa4bc0f98fa5..7e41660cf1a2 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -695,6 +695,7 @@ java_api_library {
"api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -710,6 +711,7 @@ java_api_library {
"system-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -727,6 +729,7 @@ java_api_library {
"test-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -742,6 +745,7 @@ java_api_library {
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
],
+ enable_validation: false,
}
java_api_library {
@@ -761,6 +765,7 @@ java_api_library {
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -774,6 +779,7 @@ java_api_library {
"stub-annotations",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -798,6 +804,7 @@ java_api_library {
visibility: [
"//visibility:private",
],
+ enable_validation: false,
}
java_api_library {
@@ -814,6 +821,7 @@ java_api_library {
"android_module_lib_stubs_current.from-text",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
////////////////////////////////////////////////////////////////////////
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 1f023bd39520..d9e72b83e46e 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -53,16 +53,6 @@ android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unre
android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101]
-android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101]
-android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101]
-android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101]
-android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101]
-android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101]
android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
@@ -83,33 +73,6 @@ android/app/appsearch/SearchSpec.java:244: lint: Unresolved link/see tag "Featur
android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
-android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101]
-android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101]
-android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101]
-android/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101]
-android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101]
-android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [101]
-android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101]
-android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101]
-android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101]
-android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101]
-android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101]
-android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101]
-android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101]
-android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [101]
-android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101]
-android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101]
android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101]
@@ -135,11 +98,6 @@ android/hardware/camera2/CaptureRequest.java:704: lint: Unresolved link/see tag
android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]
android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
-android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101]
-android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101]
-android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101]
-android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]
android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]
android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101]
@@ -158,8 +116,6 @@ android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.medi
android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]
android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN}, {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN}, {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED}, {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG}, {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY}, {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY}, {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101]
-android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101]
-android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101]
android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]
android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]
android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101]
@@ -195,63 +151,6 @@ android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "a
android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101]
-android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101]
-android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101]
-android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101]
-android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101]
-android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101]
-android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101]
-android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101]
-android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101]
-android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101]
-android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101]
-android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101]
-android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [101]
-android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101]
-android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101]
-android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101]
-android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101]
-android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101]
-android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101]
-android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101]
-android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101]
-android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101]
-android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101]
-android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101]
-android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101]
-android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101]
-android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101]
-android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101]
-android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101]
-android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101]
-android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101]
-android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101]
-android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101]
android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
@@ -261,21 +160,11 @@ android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/se
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101]
-android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101]
-android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101]
com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101]
-com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [101]
com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]
com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]
com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101]
-com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101]
com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]
com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]
com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101]
@@ -283,13 +172,3 @@ com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/se
com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]
com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
-
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131]
-android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131]
-android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131]
-android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131]
-android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106]
-
-java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103]
diff --git a/core/api/current.txt b/core/api/current.txt
index 20424beb2447..69a3cb35fa61 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public final class Manifest {
@@ -3288,10 +3286,10 @@ package android.accessibilityservice {
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
- method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
- method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
- method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
- method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+ method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
+ method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
@@ -3403,9 +3401,9 @@ package android.accessibilityservice {
field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3
field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9
field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7
- field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
- field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
- field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
field public static final String SERVICE_META_DATA = "android.accessibilityservice";
field public static final int SHOW_MODE_AUTO = 0; // 0x0
@@ -5741,6 +5739,7 @@ package android.app {
ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);
method @Deprecated public void onBackStackChanged();
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
method @Deprecated public void setActivity(android.app.Activity);
method @Deprecated public void setMaxVisible(int);
method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener);
@@ -9789,7 +9788,7 @@ package android.content {
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
- method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
+ method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
method @NonNull public android.content.AttributionSource.Builder setPid(int);
}
@@ -13653,6 +13652,7 @@ package android.content.res {
public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
method public void close();
+ method public String getAttributeNamespace(int);
}
}
@@ -14352,14 +14352,14 @@ package android.database.sqlite {
public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
method public void beginTransaction();
method public void beginTransactionNonExclusive();
- method public void beginTransactionReadOnly();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
- method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
- method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
method public static boolean deleteDatabase(@NonNull java.io.File);
method public void disableWriteAheadLogging();
@@ -14370,13 +14370,13 @@ package android.database.sqlite {
method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
- method public long getLastChangedRowCount();
- method public long getLastInsertRowId();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
method public long getMaximumSize();
method public long getPageSize();
method public String getPath();
method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
- method public long getTotalChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
method public int getVersion();
method public boolean inTransaction();
method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14598,7 @@ package android.database.sqlite {
method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
}
- public final class SQLiteRawStatement implements java.io.Closeable {
+ @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -15671,7 +15671,7 @@ package android.graphics {
public final class Gainmap implements android.os.Parcelable {
ctor public Gainmap(@NonNull android.graphics.Bitmap);
- ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
+ ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);
method public int describeContents();
method @NonNull public float getDisplayRatioForFullHdr();
method @NonNull public float[] getEpsilonHdr();
@@ -16309,7 +16309,7 @@ package android.graphics {
method public void arcTo(float, float, float, float, float, float, boolean);
method public void close();
method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean);
- method public void computeBounds(@NonNull android.graphics.RectF);
+ method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);
method public void conicTo(float, float, float, float, float);
method public void cubicTo(float, float, float, float, float, float);
method @NonNull public android.graphics.Path.FillType getFillType();
@@ -20405,7 +20405,7 @@ package android.inputmethodservice {
method @Deprecated public boolean isPreviewEnabled();
method @Deprecated public boolean isProximityCorrectionEnabled();
method @Deprecated public boolean isShifted();
- method public void onClick(android.view.View);
+ method @Deprecated public void onClick(android.view.View);
method @Deprecated public void onDetachedFromWindow();
method @Deprecated public void onDraw(android.graphics.Canvas);
method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key);
@@ -24232,7 +24232,7 @@ package android.media {
@Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {
method @Deprecated public void apply();
- method public Object clone() throws java.lang.CloneNotSupportedException;
+ method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException;
@@ -27096,6 +27096,7 @@ package android.media.tv {
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public String getSelectedTrack(int);
method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
+ method protected void onLayout(boolean, int, int, int, int);
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
method public void reset();
@@ -33094,6 +33095,22 @@ package android.os {
method public void onStateChanged(boolean);
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements android.os.Parcelable {
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int describeContents();
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public String getName();
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int getType();
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final android.os.Parcelable.Creator<android.os.PowerMonitor> CREATOR;
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; // 0x0
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; // 0x1
+ }
+
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings {
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor);
+ field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff
+ }
+
public class Process {
ctor public Process();
method public static final long getElapsedCpuTime();
@@ -33656,6 +33673,8 @@ package android.os.health {
}
public class SystemHealthManager {
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>);
+ method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
method public android.os.health.HealthStats takeMyUidSnapshot();
method public android.os.health.HealthStats takeUidSnapshot(int);
method public android.os.health.HealthStats[] takeUidSnapshots(int[]);
@@ -43531,7 +43550,6 @@ package android.telephony {
method public int getLongitude();
method public int getNetworkId();
method public int getSystemId();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
}
@@ -43547,7 +43565,6 @@ package android.telephony {
method @Nullable public String getMncString();
method @Nullable public String getMobileNetworkOperator();
method @Deprecated public int getPsc();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
}
@@ -43565,7 +43582,6 @@ package android.telephony {
method @Nullable public String getMobileNetworkOperator();
method public int getPci();
method public int getTac();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;
}
@@ -43578,7 +43594,6 @@ package android.telephony {
method @IntRange(from=0, to=3279165) public int getNrarfcn();
method @IntRange(from=0, to=1007) public int getPci();
method @IntRange(from=0, to=16777215) public int getTac();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
}
@@ -43592,7 +43607,6 @@ package android.telephony {
method @Nullable public String getMncString();
method @Nullable public String getMobileNetworkOperator();
method public int getUarfcn();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;
}
@@ -43608,7 +43622,6 @@ package android.telephony {
method @Nullable public String getMobileNetworkOperator();
method public int getPsc();
method public int getUarfcn();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;
}
@@ -43692,6 +43705,7 @@ package android.telephony {
public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getCdmaDbm();
method public int getCdmaEcio();
@@ -43702,24 +43716,28 @@ package android.telephony {
method public int getEvdoLevel();
method public int getEvdoSnr();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
}
public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getBitErrorRate();
method public int getDbm();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
method public int getRssi();
method public int getTimingAdvance();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
}
public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method @IntRange(from=0, to=15) public int getCqi();
method @IntRange(from=1, to=6) public int getCqiTableIndex();
@@ -43730,12 +43748,14 @@ package android.telephony {
method public int getRssi();
method public int getRssnr();
method public int getTimingAdvance();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;
}
public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
@@ -43748,26 +43768,31 @@ package android.telephony {
method public int getSsRsrq();
method public int getSsSinr();
method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
}
public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getDbm();
method @IntRange(from=0, to=4) public int getLevel();
method public int getRscp();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
}
public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getDbm();
method public int getEcNo();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
}
@@ -46555,6 +46580,7 @@ package android.text {
method @Deprecated public int length();
method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
method @Deprecated public CharSequence subSequence(int, int);
+ method @Deprecated public String toString();
}
@Deprecated public class AndroidCharacter {
@@ -47006,6 +47032,7 @@ package android.text {
method public void removeSpan(Object);
method public void setSpan(Object, int, int, int);
method public CharSequence subSequence(int, int);
+ method public String toString();
}
public static final class PrecomputedText.Params {
@@ -47135,6 +47162,7 @@ package android.text {
method public void setFilters(android.text.InputFilter[]);
method public void setSpan(Object, int, int, int);
method public CharSequence subSequence(int, int);
+ method public String toString();
method public static android.text.SpannableStringBuilder valueOf(CharSequence);
}
@@ -48775,7 +48803,9 @@ package android.util {
method public boolean containsValue(Object);
method public void ensureCapacity(int);
method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+ method public boolean equals(@Nullable Object);
method public V get(Object);
+ method public int hashCode();
method public int indexOfKey(Object);
method public int indexOfValue(Object);
method public boolean isEmpty();
@@ -48807,7 +48837,9 @@ package android.util {
method public boolean contains(Object);
method public boolean containsAll(java.util.Collection<?>);
method public void ensureCapacity(int);
+ method public boolean equals(@Nullable Object);
method public void forEach(java.util.function.Consumer<? super E>);
+ method public int hashCode();
method public int indexOf(Object);
method public boolean isEmpty();
method public java.util.Iterator<E> iterator();
@@ -57546,6 +57578,7 @@ package android.widget {
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int);
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
}
@Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
@@ -57624,6 +57657,7 @@ package android.widget {
method public long getSelectedItemId();
method public int getSelectedItemPosition();
method public abstract android.view.View getSelectedView();
+ method protected void onLayout(boolean, int, int, int, int);
method public boolean performItemClick(android.view.View, int, long);
method public abstract void setAdapter(T);
method public void setEmptyView(android.view.View);
@@ -58270,6 +58304,7 @@ package android.widget {
method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();
method public boolean getMeasureAllChildren();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setMeasureAllChildren(boolean);
}
@@ -58323,6 +58358,7 @@ package android.widget {
method public boolean getUseDefaultMargins();
method public boolean isColumnOrderPreserved();
method public boolean isRowOrderPreserved();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setAlignmentMode(int);
method public void setColumnCount(int);
method public void setColumnOrderPreserved(boolean);
@@ -58545,6 +58581,7 @@ package android.widget {
method public float getWeightSum();
method public boolean isBaselineAligned();
method public boolean isMeasureWithLargestChildEnabled();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setBaselineAligned(boolean);
method public void setBaselineAlignedChildIndex(int);
method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -59093,6 +59130,7 @@ package android.widget {
method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
method public int getGravity();
method public int getIgnoreGravity();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setGravity(int);
method public void setHorizontalGravity(int);
method public void setIgnoreGravity(int);
@@ -59547,6 +59585,7 @@ package android.widget {
method @Deprecated public boolean isMoving();
method @Deprecated public boolean isOpened();
method @Deprecated public void lock();
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
method @Deprecated public void open();
method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);
method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener);
@@ -60193,6 +60232,7 @@ package android.widget {
method public boolean hideOverflowMenu();
method public void inflateMenu(@MenuRes int);
method public boolean isOverflowMenuShowing();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setCollapseContentDescription(@StringRes int);
method public void setCollapseContentDescription(@Nullable CharSequence);
method public void setCollapseIcon(@DrawableRes int);
@@ -60347,7 +60387,7 @@ package android.widget {
method @Deprecated public android.view.View getZoomControls();
method @Deprecated public boolean isAutoDismissed();
method @Deprecated public boolean isVisible();
- method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);
method @Deprecated public void setAutoDismissed(boolean);
method @Deprecated public void setFocusable(boolean);
method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 500a12cacc3b..b5d3ed7c8a7e 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/core/api/module-lib-removed.txt
+++ b/core/api/module-lib-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/api/removed.txt b/core/api/removed.txt
index e2b4e4dfc6c4..5a4be65ef559 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.app {
public class Notification implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7dcc7b2cab13..c72d09d66494 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -299,6 +297,7 @@ package android {
field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+ field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
@@ -313,7 +312,7 @@ package android {
field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
- field public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+ field @FlaggedApi("backstage_power.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -3556,7 +3555,7 @@ package android.content {
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
- field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+ field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -4029,7 +4028,7 @@ package android.content.pm {
field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
- field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
+ field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -4060,7 +4059,9 @@ package android.content.pm {
}
public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+ method public int describeContents();
method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
}
@@ -5960,6 +5961,7 @@ package android.hardware.soundtrigger {
public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+ method public int describeContents();
method public int getId();
method @NonNull public java.util.Locale getLocale();
method public int getRecognitionModes();
@@ -5982,6 +5984,7 @@ package android.hardware.soundtrigger {
public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+ method public int describeContents();
method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();
method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5989,6 +5992,7 @@ package android.hardware.soundtrigger {
}
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+ method public int describeContents();
method public int getEnd();
method public int getStart();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -6716,7 +6720,7 @@ package android.media.audiopolicy {
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public String toLogFriendlyString();
- method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
+ method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
@@ -6799,6 +6803,7 @@ package android.media.musicrecognition {
public abstract class MusicRecognitionService extends android.app.Service {
ctor public MusicRecognitionService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
}
@@ -6857,6 +6862,7 @@ package android.media.soundtrigger {
public abstract class SoundTriggerDetectionService extends android.app.Service {
ctor public SoundTriggerDetectionService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int);
@@ -7570,6 +7576,7 @@ package android.media.tv.tuner.filter {
}
public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method protected void finalize();
method public long getAudioHandle();
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public long getAvDataId();
@@ -8964,6 +8971,8 @@ package android.net {
package android.net.metrics {
@Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class ApfProgramEvent.Builder {
@@ -8978,6 +8987,8 @@ package android.net.metrics {
}
@Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class ApfStats.Builder {
@@ -8996,6 +9007,8 @@ package android.net.metrics {
}
@Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class DhcpClientEvent.Builder {
@@ -9007,7 +9020,9 @@ package android.net.metrics {
@Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public DhcpErrorEvent(int);
+ method @Deprecated public int describeContents();
method @Deprecated public static int errorCodeWithOption(int, int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
@@ -9045,6 +9060,8 @@ package android.net.metrics {
@Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public IpManagerEvent(int, long);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
@@ -9057,6 +9074,8 @@ package android.net.metrics {
@Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public IpReachabilityEvent(int);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int NUD_FAILED = 512; // 0x200
field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
field @Deprecated public static final int PROBE = 256; // 0x100
@@ -9067,6 +9086,8 @@ package android.net.metrics {
@Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public NetworkEvent(int, long);
ctor @Deprecated public NetworkEvent(int);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
@@ -9083,6 +9104,8 @@ package android.net.metrics {
}
@Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class RaEvent.Builder {
@@ -9097,7 +9120,9 @@ package android.net.metrics {
}
@Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
method @Deprecated @NonNull public static String getProbeName(int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
field @Deprecated public static final int PROBE_DNS = 0; // 0x0
@@ -11502,6 +11527,7 @@ package android.service.assist.classification {
public abstract class FieldClassificationService extends android.app.Service {
ctor public FieldClassificationService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
method public void onConnected();
method public void onDisconnected();
@@ -11580,6 +11606,7 @@ package android.service.autofill.augmented {
method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -11618,6 +11645,7 @@ package android.service.autofill.augmented {
public final class FillWindow implements java.lang.AutoCloseable {
ctor public FillWindow();
+ method public void close();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
}
@@ -11643,6 +11671,7 @@ package android.service.carrier {
public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {
ctor public CarrierMessagingServiceWrapper();
method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
+ method public void close();
method public void disconnect();
method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
@@ -11693,6 +11722,7 @@ package android.service.contentcapture {
method public final void disableSelf();
method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);
method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
+ method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
@@ -11731,6 +11761,7 @@ package android.service.contentsuggestions {
public abstract class ContentSuggestionsService extends android.app.Service {
ctor public ContentSuggestionsService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);
method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle);
@@ -11744,6 +11775,7 @@ package android.service.dataloader {
public abstract class DataLoaderService extends android.app.Service {
ctor public DataLoaderService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
}
@@ -12416,6 +12448,7 @@ package android.service.tracing {
public class TraceReportService extends android.app.Service {
ctor public TraceReportService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
}
@@ -13041,6 +13074,7 @@ package android.telecom {
method @Nullable public android.content.ComponentName getCallScreeningComponent();
method public boolean isBlocked();
method public boolean isInContacts();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;
}
@@ -13350,6 +13384,7 @@ package android.telephony {
method public int getReason();
method public int getTimeoutSeconds();
method public boolean isEnabled();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
field public static final int REASON_ALL = 4; // 0x4
field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
@@ -13634,6 +13669,7 @@ package android.telephony {
method public int getDownlinkCapacityKbps();
method public int getType();
method public int getUplinkCapacityKbps();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
field public static final int INVALID = -1; // 0xffffffff
field public static final int LCE_TYPE_COMBINED = 2; // 0x2
@@ -13643,8 +13679,10 @@ package android.telephony {
public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public LteVopsSupportInfo(int, int);
+ method public boolean equals(@Nullable Object);
method public int getEmcBearerSupport();
method public int getVopsSupport();
+ method public int hashCode();
method public boolean isEmergencyServiceFallbackSupported();
method public boolean isEmergencyServiceSupported();
method public boolean isVopsSupported();
@@ -13745,9 +13783,11 @@ package android.telephony {
public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public NrVopsSupportInfo(int, int, int);
+ method public boolean equals(@Nullable Object);
method public int getEmcSupport();
method public int getEmfSupport();
method public int getVopsSupport();
+ method public int hashCode();
method public boolean isEmergencyServiceFallbackSupported();
method public boolean isEmergencyServiceSupported();
method public boolean isVopsSupported();
@@ -14860,6 +14900,7 @@ package android.telephony.data {
public abstract class QualifiedNetworksService extends android.app.Service {
ctor public QualifiedNetworksService();
+ method public android.os.IBinder onBind(android.content.Intent);
method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
}
@@ -15051,6 +15092,7 @@ package android.telephony.gba {
public class GbaService extends android.app.Service {
ctor public GbaService();
method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean);
+ method public android.os.IBinder onBind(android.content.Intent);
method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;
method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;
field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService";
@@ -15553,6 +15595,7 @@ package android.telephony.ims {
method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);
method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
+ method public android.os.IBinder onBind(android.content.Intent);
method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;
method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();
method public void readyForFeatureCreation();
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 1fa2718dc6d2..aa17df3471d7 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.app {
public class AppOpsManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index eeddeb21aa9d..acac4c20777b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -3587,8 +3585,8 @@ package android.view {
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
- field public float preferredMaxDisplayRefreshRate;
- field public float preferredMinDisplayRefreshRate;
+ field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate;
+ field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
@@ -3618,7 +3616,6 @@ package android.view.accessibility {
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
- field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff
}
}
@@ -3946,7 +3943,9 @@ package android.widget.inline {
package android.window {
public final class BackNavigationInfo implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public static String typeToString(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
field public static final String KEY_TRIGGER_BACK = "TriggerBack";
field public static final int TYPE_CALLBACK = 4; // 0x4
@@ -4007,11 +4006,13 @@ package android.window {
}
public final class TaskFragmentCreationParams implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public android.os.IBinder getFragmentToken();
method @NonNull public android.graphics.Rect getInitialRelativeBounds();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
method @NonNull public android.os.IBinder getOwnerToken();
method public int getWindowingMode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
}
@@ -4023,6 +4024,7 @@ package android.window {
}
public final class TaskFragmentInfo implements android.os.Parcelable {
+ method public int describeContents();
method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
method @NonNull public java.util.List<android.os.IBinder> getActivities();
method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment();
@@ -4036,6 +4038,7 @@ package android.window {
method public boolean isEmpty();
method public boolean isTaskClearedForReuse();
method public boolean isVisible();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
}
@@ -4058,6 +4061,8 @@ package android.window {
}
public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
}
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3370c121acfe..1000612ee0e2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -23,6 +23,7 @@ import android.accessibilityservice.GestureDescription.MotionEventGenerator;
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -793,6 +794,7 @@ public abstract class AccessibilityService extends Service {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
@IntDef(
prefix = {"OVERLAY_RESULT_"},
value = {
@@ -803,6 +805,7 @@ public abstract class AccessibilityService extends Service {
public @interface AttachOverlayResult {}
/** Result code indicating the overlay was successfully attached. */
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_SUCCESS = 0;
/**
@@ -810,6 +813,7 @@ public abstract class AccessibilityService extends Service {
* error and not
* because of problems with the input.
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;
/**
@@ -817,6 +821,7 @@ public abstract class AccessibilityService extends Service {
* specified display or
* window id was invalid.
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_INVALID = 2;
private int mConnectionId = AccessibilityInteractionClient.NO_ID;
@@ -3506,11 +3511,7 @@ public abstract class AccessibilityService extends Service {
* @param displayId the display to which the SurfaceControl should be attached.
* @param sc the SurfaceControl containing the overlay content
*
- * @deprecated Use
- * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)}
- * instead.
*/
- @Deprecated
public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
AccessibilityInteractionClient.getInstance(this)
@@ -3547,6 +3548,7 @@ public abstract class AccessibilityService extends Service {
* @see #OVERLAY_RESULT_INVALID
* @see #OVERLAY_RESULT_INTERNAL_ERROR
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public void attachAccessibilityOverlayToDisplay(
int displayId,
@NonNull SurfaceControl sc,
@@ -3581,11 +3583,7 @@ public abstract class AccessibilityService extends Service {
* @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
* @param sc the SurfaceControl containing the overlay content
*
- * @deprecated Use
- * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)}
- * instead.
*/
- @Deprecated
public void attachAccessibilityOverlayToWindow(
int accessibilityWindowId, @NonNull SurfaceControl sc) {
Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
@@ -3623,6 +3621,7 @@ public abstract class AccessibilityService extends Service {
* @see #OVERLAY_RESULT_INVALID
* @see #OVERLAY_RESULT_INTERNAL_ERROR
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public void attachAccessibilityOverlayToWindow(
int accessibilityWindowId,
@NonNull SurfaceControl sc,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 895dde760058..f2c00517ad16 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2073,9 +2073,7 @@ public class ActivityOptions extends ComponentOptions {
* Allow a {@link PendingIntent} to use the privilege of its creator to start background
* activities.
*
- * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
- * @throws IllegalArgumentException is the value is not a valid
- * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
+ * @param mode the mode being set
*/
@NonNull
public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
@@ -2088,7 +2086,7 @@ public class ActivityOptions extends ComponentOptions {
* Returns the mode to start background activities granted by the creator of the
* {@link PendingIntent}.
*
- * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+ * @return the mode currently set
*/
public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
return mPendingIntentCreatorBackgroundActivityStartMode;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 17637df90b99..ecbc9b1d52c2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1487,13 +1487,13 @@ public class AppOpsManager {
AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
- * Allows the assistant app to get the training data from the trusted process to improve the
- * hotword training model.
+ * Allows the privileged assistant app to receive the training data from the sandboxed hotword
+ * detection service.
*
* @hide
*/
- public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
- AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA;
+ public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+ AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1641,7 +1641,7 @@ public class AppOpsManager {
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA
+ OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
})
public @interface AppOpString {}
@@ -2262,13 +2262,13 @@ public class AppOpsManager {
"android:receive_sandbox_trigger_audio";
/**
- * Allows the assistant app to get the training data from the trusted process to improve
- * the hotword training model.
+ * Allows the privileged assistant app to receive training data from the sandboxed hotword
+ * detection service.
*
* @hide
*/
- public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
- "android:receive_trusted_process_training_data";
+ public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+ "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2381,7 +2381,8 @@ public class AppOpsManager {
OP_FOREGROUND_SERVICE_SPECIAL_USE,
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OP_USE_FULL_SCREEN_INTENT,
- OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2814,9 +2815,11 @@ public class AppOpsManager {
"RECEIVE_SANDBOX_TRIGGER_AUDIO")
.setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
.setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
- new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
- OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
- "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build()
+ new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+ OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
+ "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
+ .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
+ .setDefaultMode(AppOpsManager.MODE_DEFAULT).build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9549ebf698a8..41b400459526 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -843,8 +843,7 @@ public class BroadcastOptions extends ComponentOptions {
* considered to be in the same delivery group as this iff it has the same {@code namespace}
* and {@code key}.
*
- * <p> If neither matching key using this API nor matching filter using
- * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+ * <p> If not matching key using this API then by default
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
*/
@NonNull
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8fea03ba47f5..ebf183ff18c3 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -50,7 +50,6 @@ import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.security.net.config.NetworkSecurityConfigProvider;
-import android.sysprop.VndkProperties;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -901,14 +900,10 @@ public final class LoadedApk {
}
// Similar to vendor apks, we should add /product/lib for apks from product partition
- // when product apps are marked as unbundled. We cannot use the same way from vendor
- // to check if lib path exists because there is possibility that /product/lib would not
- // exist from legacy device while product apks are bundled. To make this clear, we use
- // "ro.product.vndk.version" property. If the property is defined, we regard all product
- // apks as unbundled.
+ // when product apps are marked as unbundled. Product is separated as long as the
+ // partition exists, so it can be handled with same approach from the vendor partition.
if (mApplicationInfo.getCodePath() != null
- && mApplicationInfo.isProduct()
- && VndkProperties.product_vndk_version().isPresent()) {
+ && mApplicationInfo.isProduct()) {
isBundledApp = false;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dd7db23d668b..94e1292a7554 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1514,13 +1514,13 @@ public class Notification implements Parcelable
/**
* {@link #extras} key: the color used as a hint for the Answer action button of a
- * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
*/
public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
/**
* {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
- * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
*/
public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
@@ -1704,7 +1704,7 @@ public class Notification implements Parcelable
private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
/**
- * {@link }: No semantic action defined.
+ * No semantic action defined.
*/
public static final int SEMANTIC_ACTION_NONE = 0;
@@ -7923,7 +7923,7 @@ public class Notification implements Parcelable
* (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
* conversation section in the shade above non-conversation alerting and silence notifications.
* To be a valid conversation shortcut, the shortcut must be a
- * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut.
+ * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
*
* <p>
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 15bd1dcc3980..e2689687f388 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -651,6 +651,7 @@ public class AssistStructure implements Parcelable {
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
AutofillOverlay mAutofillOverlay;
+ boolean mIsCredential;
int mX;
int mY;
@@ -799,6 +800,7 @@ public class AssistStructure implements Parcelable {
if (autofillFlags != 0) {
mSanitized = in.readInt() == 1;
+ mIsCredential = in.readInt() == 1;
mImportantForAutofill = in.readInt();
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1033,6 +1035,7 @@ public class AssistStructure implements Parcelable {
if (autofillFlags != 0) {
out.writeInt(mSanitized ? 1 : 0);
+ out.writeInt(mIsCredential ? 1 : 0);
out.writeInt(mImportantForAutofill);
writeSensitive = mSanitized || !sanitizeOnWrite;
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1246,6 +1249,19 @@ public class AssistStructure implements Parcelable {
}
/**
+ * @return whether the node is a credential.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ * TODO(b/303677885): add TestApi
+ *
+ * @hide
+ */
+ public boolean isCredential() {
+ return mIsCredential;
+ }
+
+ /**
* Gets the {@link android.text.InputType} bits of this structure.
*
* @return bits as defined by {@link android.text.InputType}.
@@ -2183,6 +2199,11 @@ public class AssistStructure implements Parcelable {
}
@Override
+ public void setIsCredential(boolean isCredential) {
+ mNode.mIsCredential = isCredential;
+ }
+
+ @Override
public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) {
mNode.mReceiveContentMimeTypes = mimeTypes;
}
@@ -2498,7 +2519,9 @@ public class AssistStructure implements Parcelable {
+ ", value=" + node.getAutofillValue()
+ ", sanitized=" + node.isSanitized()
+ ", important=" + node.getImportantForAutofill()
- + ", visibility=" + node.getVisibility());
+ + ", visibility=" + node.getVisibility()
+ + ", isCredential=" + node.isCredential()
+ );
}
final int NCHILDREN = node.getChildCount();
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
new file mode 100644
index 000000000000..98281338872b
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import java.util.Objects;
+
+/**
+ * Message to deliver window resize info.
+ * @hide
+ */
+public class WindowStateResizeItem extends ClientTransactionItem {
+
+ private IWindow mWindow;
+ private ClientWindowFrames mFrames;
+ private boolean mReportDraw;
+ private MergedConfiguration mConfiguration;
+ private InsetsState mInsetsState;
+ private boolean mForceLayout;
+ private boolean mAlwaysConsumeSystemBars;
+ private int mDisplayId;
+ private int mSyncSeqId;
+ private boolean mDragResizing;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client,
+ @NonNull PendingTransactionActions pendingActions) {
+ try {
+ mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout,
+ mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing);
+ } catch (RemoteException e) {
+ // Should be a local call.
+ throw new RuntimeException(e);
+ }
+ }
+
+ // ObjectPoolItem implementation
+
+ private WindowStateResizeItem() {}
+
+ /** Obtains an instance initialized with provided params. */
+ public static WindowStateResizeItem obtain(@NonNull IWindow window,
+ @NonNull ClientWindowFrames frames, boolean reportDraw,
+ @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState,
+ boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
+ boolean dragResizing) {
+ WindowStateResizeItem instance =
+ ObjectPool.obtain(WindowStateResizeItem.class);
+ if (instance == null) {
+ instance = new WindowStateResizeItem();
+ }
+ instance.mWindow = requireNonNull(window);
+ instance.mFrames = requireNonNull(frames);
+ instance.mReportDraw = reportDraw;
+ instance.mConfiguration = requireNonNull(configuration);
+ instance.mInsetsState = requireNonNull(insetsState);
+ instance.mForceLayout = forceLayout;
+ instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+ instance.mDisplayId = displayId;
+ instance.mSyncSeqId = syncSeqId;
+ instance.mDragResizing = dragResizing;
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ mWindow = null;
+ mFrames = null;
+ mReportDraw = false;
+ mConfiguration = null;
+ mInsetsState = null;
+ mForceLayout = false;
+ mAlwaysConsumeSystemBars = false;
+ mDisplayId = INVALID_DISPLAY;
+ mSyncSeqId = -1;
+ mDragResizing = false;
+ ObjectPool.recycle(this);
+ }
+
+ // Parcelable implementation
+
+ /** Writes to Parcel. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mWindow.asBinder());
+ dest.writeTypedObject(mFrames, flags);
+ dest.writeBoolean(mReportDraw);
+ dest.writeTypedObject(mConfiguration, flags);
+ dest.writeTypedObject(mInsetsState, flags);
+ dest.writeBoolean(mForceLayout);
+ dest.writeBoolean(mAlwaysConsumeSystemBars);
+ dest.writeInt(mDisplayId);
+ dest.writeInt(mSyncSeqId);
+ dest.writeBoolean(mDragResizing);
+ }
+
+ /** Reads from Parcel. */
+ private WindowStateResizeItem(@NonNull Parcel in) {
+ mWindow = IWindow.Stub.asInterface(in.readStrongBinder());
+ mFrames = in.readTypedObject(ClientWindowFrames.CREATOR);
+ mReportDraw = in.readBoolean();
+ mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR);
+ mInsetsState = in.readTypedObject(InsetsState.CREATOR);
+ mForceLayout = in.readBoolean();
+ mAlwaysConsumeSystemBars = in.readBoolean();
+ mDisplayId = in.readInt();
+ mSyncSeqId = in.readInt();
+ mDragResizing = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() {
+ public WindowStateResizeItem createFromParcel(@NonNull Parcel in) {
+ return new WindowStateResizeItem(in);
+ }
+
+ public WindowStateResizeItem[] newArray(int size) {
+ return new WindowStateResizeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowStateResizeItem other = (WindowStateResizeItem) o;
+ return Objects.equals(mWindow, other.mWindow)
+ && Objects.equals(mFrames, other.mFrames)
+ && mReportDraw == other.mReportDraw
+ && Objects.equals(mConfiguration, other.mConfiguration)
+ && Objects.equals(mInsetsState, other.mInsetsState)
+ && mForceLayout == other.mForceLayout
+ && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars
+ && mDisplayId == other.mDisplayId
+ && mSyncSeqId == other.mSyncSeqId
+ && mDragResizing == other.mDragResizing;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mWindow);
+ result = 31 * result + Objects.hashCode(mFrames);
+ result = 31 * result + (mReportDraw ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mConfiguration);
+ result = 31 * result + Objects.hashCode(mInsetsState);
+ result = 31 * result + (mForceLayout ? 1 : 0);
+ result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0);
+ result = 31 * result + mDisplayId;
+ result = 31 * result + mSyncSeqId;
+ result = 31 * result + (mDragResizing ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowStateResizeItem{window=" + mWindow
+ + ", reportDrawn=" + mReportDraw
+ + ", configuration=" + mConfiguration
+ + "}";
+ }
+}
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index afe87de1dbf5..d1f90676a15f 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -7,3 +7,9 @@ flag {
bug: "296061232"
}
+flag {
+ name: "report_usage_stats_permission"
+ namespace: "backstage_power"
+ description: "Feature flag for the new REPORT_USAGE_STATS permission."
+ bug: "296056771"
+}
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 03e75e9fc483..570ecaa47b4e 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -161,16 +161,16 @@ public abstract class CompanionDeviceService extends Service {
public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
/**
- * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will
- * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
- * device has appeared on its own.
+ * A companion app for a self-managed device will receive the callback
+ * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
+ * own.
*/
public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
/**
- * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will
- * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a
- * device has disappeared on its own.
+ * A companion app for a self-managed device will receive the callback
+ * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
+ * its own.
*/
public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 0af4c92e0b63..93a3e7822888 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -33,9 +33,6 @@ import android.os.RemoteException;
*
* <p>Read-only device representation exposing the properties of an existing virtual device.
*
- * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
- * by the virtual device creator and allows them to manage the device.
- *
* @see VirtualDeviceManager#registerVirtualDeviceListener
*/
public final class VirtualDevice implements Parcelable {
@@ -120,8 +117,6 @@ public final class VirtualDevice implements Parcelable {
/**
* Returns the name of the virtual device (optionally) provided during its creation.
- *
- * @see VirtualDeviceParams.Builder#setName(String)
*/
public @Nullable String getName() {
return mName;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index bf78dd09e7c2..b9451a74f9e9 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -46,15 +46,15 @@ import java.util.concurrent.atomic.AtomicLong;
* <pre>
* VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
* VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
- * @Override
+ * {@literal @}Override
* public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
* writer.addChannel(channelHandle, sharedMemory);
* }
- * @Override
+ * {@literal @}Override
* public void onDirectChannelDestroyed(int channelHandle);
* writer.removeChannel(channelHandle);
* }
- * @Override
+ * {@literal @}Override
* public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
* int reportToken)
* if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bfc1eec829e8..62fbcaff79e3 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -730,7 +730,7 @@ public final class AttributionSource implements Parcelable {
/**
* The next app to receive the permission protected data.
*
- * @deprecated Use {@link setNextAttributionSource} instead.
+ * @deprecated Use {@link #setNextAttributionSource} instead.
*/
@Deprecated
public @NonNull Builder setNext(@Nullable AttributionSource value) {
@@ -744,6 +744,7 @@ public final class AttributionSource implements Parcelable {
/**
* The next app to receive the permission protected data.
*/
+ @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
checkNotUsed();
mBuilderFieldsSet |= 0x20;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b6a98a5b8f83..59bb73b5916d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -323,7 +323,7 @@ public abstract class Context {
// Make sure no flag uses the sign bit (most significant bit) of the long integer,
// to avoid future confusion.
BIND_BYPASS_USER_NETWORK_RESTRICTIONS,
- BIND_FILTER_OUT_QUARANTINED_COMPONENTS,
+ BIND_MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlagsLongBits {}
@@ -703,7 +703,7 @@ public abstract class Context {
*
* @hide
*/
- public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
+ public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
/**
@@ -4357,7 +4357,6 @@ public abstract class Context {
* @see android.telephony.CarrierConfigManager
* @see #EUICC_SERVICE
* @see android.telephony.euicc.EuiccManager
- * @see android.telephony.MmsManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5f4c05f001ff..d2b23083ab64 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4180,7 +4180,7 @@ public class Intent implements Parcelable, Cloneable {
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_AVAILABLE =
@@ -4194,7 +4194,7 @@ public class Intent implements Parcelable, Cloneable {
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
- * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * a generic broadcast for all profile users.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_UNAVAILABLE =
@@ -4222,7 +4222,7 @@ public class Intent implements Parcelable, Cloneable {
* that was removed.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
* It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a
* managed user is removed.
*
@@ -4242,7 +4242,7 @@ public class Intent implements Parcelable, Cloneable {
* that was added.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
* It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a
* managed user is added.
*
@@ -5323,6 +5323,7 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
// ---------------------------------------------------------------------
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 45338bb2c0a2..1b60f8ed904f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -838,7 +838,7 @@ public abstract class PackageManager {
GET_DISABLED_COMPONENTS,
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
- FILTER_OUT_QUARANTINED_COMPONENTS,
+ MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComponentInfoFlagsBits {}
@@ -863,7 +863,7 @@ public abstract class PackageManager {
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
MATCH_CLONE_PROFILE,
- FILTER_OUT_QUARANTINED_COMPONENTS,
+ MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResolveInfoFlagsBits {}
@@ -1252,12 +1252,13 @@ public abstract class PackageManager {
*/
// TODO(b/278553670) Unhide and update @links before launch.
@SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
/**
* @hide
*/
- public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L;
+ public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
/**
* Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
@@ -4605,6 +4606,16 @@ public abstract class PackageManager {
public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
"android.software.wallet_location_based_suggestions";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the rotary encoder hardware to support rotating bezel on watch.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
+ "android.hardware.rotaryencoder.lowres";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index c3440043a4c0..8f07d19a8592 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -18,7 +18,7 @@ package android.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -28,8 +28,8 @@ import java.util.concurrent.Executor;
/**
* Represents an error encountered during the
- * {@link CredentialManager#createCredential(CreateCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class CreateCredentialException extends Exception {
/**
@@ -41,7 +41,7 @@ public class CreateCredentialException extends Exception {
/**
* The error type value for when no create options are available from any provider(s),
- * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity,
+ * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest,
* CancellationSignal, Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index db71624cbe89..755e6590a934 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -155,8 +155,7 @@ public final class CredentialDescription implements Parcelable {
}
/**
- * {@link CredentialDescription#mType} and
- * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor
+ * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor
* enforces {@link CredentialEntry} to have the same type and
* {@link android.app.slice.Slice} contained by the entry can not be hashed.
*/
@@ -166,8 +165,7 @@ public final class CredentialDescription implements Parcelable {
}
/**
- * {@link CredentialDescription#mType} and
- * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check.
+ * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check.
*/
@Override
public boolean equals(Object obj) {
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 8503072838d1..38fbd726a4fb 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -128,6 +128,11 @@ public final class CredentialProviderInfo implements Parcelable {
@TestApi
@FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
public CharSequence getSettingsActivity() {
+ // Add a manual check to make sure this returns null if
+ // the flag is not enabled.
+ if (!Flags.settingsActivityEnabled()) {
+ return null;
+ }
return mSettingsActivity;
}
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 720c53ba51cd..0421d1f5aba0 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -18,7 +18,7 @@ package android.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
+import android.content.Context;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -28,8 +28,8 @@ import java.util.concurrent.Executor;
/**
* Represents an error encountered during the
- * {@link CredentialManager#getCredential(GetCredentialRequest,
- * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ * {@link CredentialManager#getCredential(Context, GetCredentialRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class GetCredentialException extends Exception {
/**
@@ -41,7 +41,7 @@ public class GetCredentialException extends Exception {
/**
* The error type value for when no credential is found available for the given {@link
- * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
+ * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal,
* Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 056b18a51b6d..212f5716d041 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -22,7 +22,6 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.IntentSender;
@@ -36,7 +35,7 @@ import java.util.concurrent.Executor;
/**
* A response object that prefetches user app credentials and provides metadata about them. It can
* then be used to issue the full credential retrieval flow via the
- * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
* Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection
* and credential selection, to officially retrieve a credential.
*/
@@ -44,7 +43,7 @@ public final class PrepareGetCredentialResponse {
/**
* A handle that represents a pending get-credential operation. Pass this handle to {@link
- * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal,
* Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a
* credential.
*/
@@ -144,7 +143,7 @@ public final class PrepareGetCredentialResponse {
/**
* Returns a handle that represents this pending get-credential operation. Pass this handle to
- * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity,
+ * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle,
* CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially
* retrieve a credential.
*/
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 746f2f23fd5c..b003e75b4e50 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -700,6 +701,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* }
* </pre>
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
public void beginTransactionReadOnly() {
beginTransactionWithListenerReadOnly(null);
}
@@ -783,6 +785,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* }
* </pre>
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
public void beginTransactionWithListenerReadOnly(
@Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2221,6 +2224,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @throws IllegalStateException if a transaction is not in progress.
* @throws SQLiteException if the SQL cannot be compiled.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
@NonNull
public SQLiteRawStatement createRawStatement(@NonNull String sql) {
Objects.requireNonNull(sql);
@@ -2240,6 +2244,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @return The ROWID of the last row to be inserted under this connection.
* @throws IllegalStateException if there is no current transaction.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
public long getLastInsertRowId() {
return getThreadSession().getLastInsertRowId();
}
@@ -2253,6 +2258,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @return The number of rows changed by the most recent sql statement
* @throws IllegalStateException if there is no current transaction.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
public long getLastChangedRowCount() {
return getThreadSession().getLastChangedRowCount();
}
@@ -2280,6 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @return The number of rows changed on the current connection.
* @throws IllegalStateException if there is no current transaction.
*/
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
public long getTotalChangedRowCount() {
return getThreadSession().getTotalChangedRowCount();
}
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 165f1810c894..827420f96fed 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,6 +71,7 @@ import java.util.Objects;
*
* @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
*/
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
public final class SQLiteRawStatement implements Closeable {
private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
new file mode 100644
index 000000000000..564df039456b
--- /dev/null
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.database.sqlite"
+
+flag {
+ name: "sqlite_apis_15"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "SQLite APIs held back for Android 15"
+ bug: "279043253"
+}
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 99f3d156cfa9..53a9a75fbca0 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -59,7 +59,8 @@ public final class BrightnessInfo implements Parcelable {
@IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
BRIGHTNESS_MAX_REASON_NONE,
- BRIGHTNESS_MAX_REASON_THERMAL
+ BRIGHTNESS_MAX_REASON_THERMAL,
+ BRIGHTNESS_MAX_REASON_POWER_IC
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessMaxReason {}
@@ -74,6 +75,11 @@ public final class BrightnessInfo implements Parcelable {
*/
public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1;
+ /**
+ * Maximum brightness is restricted due to power throttling.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2;
+
/** Brightness */
public final float brightness;
@@ -144,6 +150,8 @@ public final class BrightnessInfo implements Parcelable {
return "none";
case BRIGHTNESS_MAX_REASON_THERMAL:
return "thermal";
+ case BRIGHTNESS_MAX_REASON_POWER_IC:
+ return "power IC";
}
return "invalid";
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index f347909348c8..aeddd0c8d4b1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -31,6 +31,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -775,7 +776,8 @@ public final class DisplayManager {
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventsMask long eventsMask) {
- mGlobal.registerDisplayListener(listener, handler, eventsMask);
+ mGlobal.registerDisplayListener(listener, handler, eventsMask,
+ ActivityThread.currentPackageName());
}
/**
@@ -1819,6 +1821,12 @@ public final class DisplayManager {
String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
/**
+ * Key for the power throttling data as a String formatted, from the display
+ * device config.
+ */
+ String KEY_POWER_THROTTLING_DATA = "power_throttling_data";
+
+ /**
* Key for new power controller feature flag. If enabled new DisplayPowerController will
* be used.
* Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 6d6085b471d9..8decd50664b3 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -25,6 +25,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -45,8 +46,11 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
+import android.sysprop.DisplayProperties;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -72,7 +76,13 @@ import java.util.concurrent.atomic.AtomicLong;
*/
public final class DisplayManagerGlobal {
private static final String TAG = "DisplayManager";
- private static final boolean DEBUG = false;
+
+ private static final String EXTRA_LOGGING_PACKAGE_NAME =
+ DisplayProperties.debug_vri_package().orElse(null);
+ private static String sCurrentPackageName = ActivityThread.currentPackageName();
+ private static boolean sExtraDisplayListenerLogging = initExtraLogging();
+
+ private static final boolean DEBUG = false || sExtraDisplayListenerLogging;
// True if display info and display ids should be cached.
//
@@ -130,6 +140,8 @@ public final class DisplayManagerGlobal {
@VisibleForTesting
public DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
+ initExtraLogging();
+
try {
mWideColorSpace =
ColorSpace.get(
@@ -208,7 +220,7 @@ public final class DisplayManagerGlobal {
registerCallbackIfNeededLocked();
- if (DEBUG) {
+ if (DEBUG || extraLogging()) {
Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
}
return info;
@@ -321,12 +333,14 @@ public final class DisplayManagerGlobal {
* If null, listener will use the handler for the current thread, and if still null,
* the handler for the main thread.
* If that is still null, a runtime exception will be thrown.
+ * @param packageName of the calling package.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
- @Nullable Handler handler, @EventsMask long eventsMask) {
+ @Nullable Handler handler, @EventsMask long eventsMask, String packageName) {
Looper looper = getLooperForHandler(handler);
Handler springBoard = new Handler(looper);
- registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask);
+ registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask,
+ packageName);
}
/**
@@ -334,9 +348,11 @@ public final class DisplayManagerGlobal {
*
* @param listener The listener that will be called when display changes occur.
* @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
+ * @param eventsMask Mask of events to be listened to.
+ * @param packageName of the calling package.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
- @NonNull Executor executor, @EventsMask long eventsMask) {
+ @NonNull Executor executor, @EventsMask long eventsMask, String packageName) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -345,15 +361,22 @@ public final class DisplayManagerGlobal {
throw new IllegalArgumentException("The set of events to listen to must not be empty.");
}
+ if (extraLogging()) {
+ Slog.i(TAG, "Registering Display Listener: "
+ + Long.toBinaryString(eventsMask) + ", packageName: " + packageName);
+ }
+
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index < 0) {
- mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask));
+ mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask,
+ packageName));
registerCallbackIfNeededLocked();
} else {
mDisplayListeners.get(index).setEventsMask(eventsMask);
}
updateCallbackIfNeededLocked();
+ maybeLogAllDisplayListeners();
}
}
@@ -362,6 +385,10 @@ public final class DisplayManagerGlobal {
throw new IllegalArgumentException("listener must not be null");
}
+ if (extraLogging()) {
+ Slog.i(TAG, "Unregistering Display Listener: " + listener);
+ }
+
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index >= 0) {
@@ -371,6 +398,18 @@ public final class DisplayManagerGlobal {
updateCallbackIfNeededLocked();
}
}
+ maybeLogAllDisplayListeners();
+ }
+
+ private void maybeLogAllDisplayListeners() {
+ if (!sExtraDisplayListenerLogging) {
+ return;
+ }
+
+ Slog.i(TAG, "Currently Registered Display Listeners:");
+ for (int i = 0; i < mDisplayListeners.size(); i++) {
+ Slog.i(TAG, i + ": " + mDisplayListeners.get(i));
+ }
}
private static Looper getLooperForHandler(@Nullable Handler handler) {
@@ -1148,15 +1187,20 @@ public final class DisplayManagerGlobal {
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Executor mExecutor;
private AtomicLong mGenerationId = new AtomicLong(1);
+ private final String mPackageName;
DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
- @EventsMask long eventsMask) {
+ @EventsMask long eventsMask, String packageName) {
mExecutor = executor;
mListener = listener;
mEventsMask = eventsMask;
+ mPackageName = packageName;
}
public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending Display Event: " + eventToString(event));
+ }
long generationId = mGenerationId.get();
Message msg = Message.obtain(null, event, displayId, 0, info);
mExecutor.execute(() -> {
@@ -1177,6 +1221,14 @@ public final class DisplayManagerGlobal {
}
private void handleMessage(Message msg) {
+ if (extraLogging()) {
+ Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what)
+ + ", display=" + msg.arg1
+ + ", mEventsMask=" + Long.toBinaryString(mEventsMask)
+ + ", mPackageName=" + mPackageName
+ + ", msg.obj=" + msg.obj
+ + ", listener=" + mListener.getClass() + ")");
+ }
if (DEBUG) {
Trace.beginSection(
"DisplayListenerDelegate(" + eventToString(msg.what)
@@ -1193,6 +1245,10 @@ public final class DisplayManagerGlobal {
if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
DisplayInfo newInfo = (DisplayInfo) msg.obj;
if (newInfo != null && !newInfo.equals(mDisplayInfo)) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
+ + newInfo);
+ }
mDisplayInfo.copyFrom(newInfo);
mListener.onDisplayChanged(msg.arg1);
}
@@ -1228,6 +1284,11 @@ public final class DisplayManagerGlobal {
Trace.endSection();
}
}
+
+ @Override
+ public String toString() {
+ return "mask: {" + mEventsMask + "}, for " + mListener.getClass();
+ }
}
/**
@@ -1353,4 +1414,19 @@ public final class DisplayManagerGlobal {
}
return "UNKNOWN";
}
+
+
+ private static boolean initExtraLogging() {
+ if (sCurrentPackageName == null) {
+ sCurrentPackageName = ActivityThread.currentPackageName();
+ sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
+ && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
+ }
+ return sExtraDisplayListenerLogging;
+ }
+
+ private static boolean extraLogging() {
+ return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals(
+ sCurrentPackageName);
+ }
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ff1a6acd8e4e..7b8419e6c6f7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1373,7 +1373,7 @@ public final class InputManager {
public interface InputDeviceListener {
/**
* Called whenever an input device has been added to the system.
- * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
+ * Use {@link #getInputDevice(int)} to get more information about the device.
*
* @param deviceId The id of the input device that was added.
*/
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 582c5c0c4dd4..4c2bbc18f2db 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3231,7 +3231,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Called when the user tapped or clicked an {@link android.widget.Editor}.
+ * Called when the user tapped or clicked an editor.
* This can be useful when IME makes a decision of showing Virtual keyboard based on what
* {@link MotionEvent#getToolType(int)} was used to click the editor.
* e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8482945dc1f0..e85b7bf1c91e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1854,7 +1854,7 @@ public abstract class BatteryStats {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public HistoryItem next;
- // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
+ // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().
@UnsupportedAppUsage
public long time;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 49d7e8bc5632..32840d4b5837 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -300,6 +300,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
*/
+ // TODO(b/298459065): switch to monotonic clock
public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
mFromTimestamp = fromTimestamp;
mToTimestamp = toTimestamp;
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 086b0e5d4b05..58def6ef7961 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -143,10 +143,11 @@ public final class BugreportManager {
*/
public void onError(@BugreportErrorCode int errorCode) {}
- /** Called when taking bugreport finishes successfully.
+ /**
+ * Called when taking bugreport finishes successfully.
*
* <p>This callback will be invoked if the
- * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
+ * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
*/
public void onFinished() {}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4174c1c13c94..fce715ade5af 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2670,7 +2670,7 @@ public final class PowerManager {
/**
* Called when overall thermal throttling status changed.
- * @param status defined in {@link android.os.Temperature}.
+ * @param status the status
*/
void onThermalStatusChanged(@ThermalStatus int status);
}
diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java
index 5fb0df7febc1..8c5f2cd48f61 100644
--- a/core/java/android/os/PowerMonitor.java
+++ b/core/java/android/os/PowerMonitor.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -23,12 +24,19 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an
- * EnergyConsumer, as defined in
- * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a>
- *
- * @hide
+ * A PowerMonitor represents either an ODPM rail (on-device power rail monitor) or a modeled
+ * energy consumer.
+ * <p/>
+ * ODPM rail names are device-specific. No assumptions should be made about the names and
+ * exact purpose of ODPM rails across different device models. A rail name may be something
+ * like "S2S_VDD_G3D"; specific knowledge of the device hardware is required to interpret
+ * the corresponding power monitor data.
+ * <p/>
+ * Energy consumer have more human-readable names, e.g. "GPU", "MODEM" etc. However, developers
+ * must be extra cautious about using energy consumers across different device models,
+ * as their exact implementations are also hardware dependent and are customized by OEMs.
*/
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public final class PowerMonitor implements Parcelable {
/**
@@ -36,9 +44,9 @@ public final class PowerMonitor implements Parcelable {
* power rail measurement, or modeled in some fashion. For example, an energy consumer may
* represent a combination of multiple rails or a portion of a rail shared between subsystems,
* e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail.
- * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType),
- * others are not.
+ * Some consumer names are standardized, others are not.
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public static final int POWER_MONITOR_TYPE_CONSUMER = 0;
/**
@@ -46,6 +54,7 @@ public final class PowerMonitor implements Parcelable {
* no assumptions can be made about the source of those measurements across different devices,
* even if they have the same name.
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;
/** @hide */
@@ -64,38 +73,60 @@ public final class PowerMonitor implements Parcelable {
* @hide
*/
public final int index;
+
@PowerMonitorType
- public final int type;
+ private final int mType;
@NonNull
- public final String name;
+ private final String mName;
/**
* @hide
*/
public PowerMonitor(int index, int type, @NonNull String name) {
this.index = index;
- this.type = type;
- this.name = name;
+ this.mType = type;
+ this.mName = name;
+ }
+
+ /**
+ * Returns the type of the power monitor.
+ */
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+ @PowerMonitorType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the power monitor, either a power rail or an energy consumer.
+ */
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+ @NonNull
+ public String getName() {
+ return mName;
}
private PowerMonitor(Parcel in) {
index = in.readInt();
- type = in.readInt();
- name = in.readString();
+ mType = in.readInt();
+ mName = in.readString8();
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(index);
- dest.writeInt(type);
- dest.writeString(name);
+ dest.writeInt(mType);
+ dest.writeString8(mName);
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@Override
public int describeContents() {
return 0;
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@NonNull
public static final Creator<PowerMonitor> CREATOR = new Creator<>() {
@Override
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index e76705917c7a..bb677d529507 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import java.util.Arrays;
@@ -24,10 +25,10 @@ import java.util.Comparator;
/**
* A collection of energy measurements from Power Monitors.
- *
- * @hide
*/
+@FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public final class PowerMonitorReadings {
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public static final int ENERGY_UNAVAILABLE = -1;
@NonNull
@@ -41,7 +42,7 @@ public final class PowerMonitorReadings {
Comparator.comparingInt(pm -> pm.index);
/**
- * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index
+ * @param powerMonitors array of power monitor, sorted by PowerMonitor.index
* @hide
*/
public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
@@ -56,7 +57,8 @@ public final class PowerMonitorReadings {
* Does not persist across reboots.
* Represents total energy: both on-battery and plugged-in.
*/
- public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) {
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
+ public long getConsumedEnergy(@NonNull PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
if (offset >= 0) {
return mEnergyUws[offset];
@@ -67,6 +69,7 @@ public final class PowerMonitorReadings {
/**
* Elapsed realtime, in milliseconds, when the snapshot was taken.
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
@ElapsedRealtimeLong
public long getTimestamp(@NonNull PowerMonitor powerMonitor) {
int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);
@@ -84,7 +87,7 @@ public final class PowerMonitorReadings {
if (i != 0) {
sb.append(", ");
}
- sb.append(mPowerMonitors[i].name)
+ sb.append(mPowerMonitors[i].getName())
.append(" = ").append(mEnergyUws[i])
.append(" (").append(mTimestampsMs[i]).append(')');
}
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 970f4192c36c..ace5f7e8393f 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -66,7 +66,7 @@ public class RemoteException extends AndroidException {
/**
* Rethrow this exception when we know it came from the system server. This
* gives us an opportunity to throw a nice clean
- * {@link DeadSystemRuntimeException} signal to avoid spamming logs with
+ * {@code DeadSystemRuntimeException} signal to avoid spamming logs with
* misleading stack traces.
* <p>
* Apps making calls into the system server may end up persisting internal
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 37559b32e841..7fceda4a4393 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@ flag {
flag {
name: "remove_app_profiler_pss_collection"
- namespace: "android_platform_power_optimization"
+ namespace: "power_optimization"
description: "Replaces background PSS collection in AppProfiler with RSS"
bug: "297542292"
}
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index dfc43f46c57d..2d53341be654 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -16,6 +16,7 @@
package android.os.health;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
@@ -24,7 +25,6 @@ import android.content.Context;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.IPowerStatsService;
import android.os.PowerMonitor;
@@ -157,39 +157,15 @@ public class SystemHealthManager {
}
/**
- * Returns a list of supported power monitors, which include raw ODPM rails and
- * modeled energy consumers. If ODPM is unsupported by PowerStats HAL, this method returns
- * an empty array.
+ * Asynchronously retrieves a list of supported {@link PowerMonitor}'s, which include raw ODPM
+ * (on-device power rail monitor) rails and modeled energy consumers. If ODPM is unsupported
+ * on this device this method delivers an empty list.
*
- * @hide
- */
- @NonNull
- public List<PowerMonitor> getSupportedPowerMonitors() {
- synchronized (mPowerMonitorsLock) {
- if (mPowerMonitorsInfo != null) {
- return mPowerMonitorsInfo;
- }
- }
- ConditionVariable lock = new ConditionVariable();
- // Populate mPowerMonitorsInfo by side-effect
- getSupportedPowerMonitors(null, unused -> lock.open());
- lock.block();
-
- synchronized (mPowerMonitorsLock) {
- return mPowerMonitorsInfo;
- }
- }
-
- /**
- * Asynchronously retrieves a list of supported power monitors, see
- * {@link #getSupportedPowerMonitors()}
- *
- * @param handler optional Handler to deliver the callback. If not supplied, the callback
- * may be invoked on an arbitrary thread.
+ * @param handler optional Handler to deliver the callback. If not supplied, the callback
+ * may be invoked on an arbitrary thread.
* @param onResult callback for the result
- *
- * @hide
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public void getSupportedPowerMonitors(@Nullable Handler handler,
@NonNull Consumer<List<PowerMonitor>> onResult) {
final List<PowerMonitor> result;
@@ -229,35 +205,6 @@ public class SystemHealthManager {
}
}
- /**
- * Retrieves the accumulated power consumption reported by the specified power monitors.
- *
- * @param powerMonitors power monitors to be returned.
- *
- * @hide
- */
- @NonNull
- public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) {
- PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1];
- RuntimeException[] outException = new RuntimeException[1];
- ConditionVariable lock = new ConditionVariable();
- getPowerMonitorReadings(powerMonitors, null,
- pms -> {
- outReadings[0] = pms;
- lock.open();
- },
- error -> {
- outException[0] = error;
- lock.open();
- }
- );
- lock.block();
- if (outException[0] != null) {
- throw outException[0];
- }
- return outReadings[0];
- }
-
private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
Comparator.comparingInt(pm -> pm.index);
@@ -270,9 +217,8 @@ public class SystemHealthManager {
* may be invoked on an arbitrary thread.
* @param onSuccess callback for the result
* @param onError callback invoked in case of an error
- *
- * @hide
*/
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
@Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,
@NonNull Consumer<RuntimeException> onError) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 88f62f327fdd..66ad12c7559a 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -27,3 +27,13 @@ flag {
description: "Enables the APIs for vibration serialization/deserialization."
bug: "245129509"
}
+
+flag {
+ namespace: "haptics"
+ name: "haptic_feedback_vibration_oem_customization_enabled"
+ description: "Enables OEMs/devices to customize vibrations for haptic feedback"
+ # Make read only. This is because the flag is used only once, and this could happen before
+ # the read-write flag values propagate to the device.
+ is_fixed_read_only: true
+ bug: "291128479"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d8534dd5fbc1..d60d4c6372fe 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -21,3 +21,10 @@ flag {
description: "enable role controller in system server"
bug: "302562590"
}
+
+flag {
+ name: "set_next_attribution_source"
+ namespace: "permissions"
+ description: "enable AttributionSource.setNextAttributionSource"
+ bug: "304478648"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b19a034d4628..b34e09f2c97a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1800,7 +1800,6 @@ public final class Settings {
* Input: Nothing.
* <p>
* Output: Nothing.
- * @see android.service.notification.NotificationAssistantService
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS =
@@ -2507,8 +2506,8 @@ public final class Settings {
* to the caller package.
* <p>
* <b>NOTE: </b> Applications should call
- * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()}
- * and only use this action to start an activity if they return {@code false}.
+ * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService(
+ * ComponentName)} and only use this action to start an activity if they return {@code false}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREDENTIAL_PROVIDER =
@@ -12375,7 +12374,7 @@ public final class Settings {
* Value to specify if the device's UTC system clock should be set automatically, e.g. using
* telephony signals like NITZ, or other sources like GNSS or NTP.
*
- * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+ * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
* automatic time detection instead of directly observing this setting as it may be ignored
* by the time_detector service under various conditions.
*
@@ -12388,7 +12387,7 @@ public final class Settings {
* Value to specify if the device's time zone system property should be set automatically,
* e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location.
*
- * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+ * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
* automatic time zone detection instead of directly observing this setting as it may be
* ignored by the time_zone_detector service under various conditions.
*
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 3fb94ee93c14..7ea74d375ffb 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -406,7 +406,7 @@ public final class FillResponse implements Parcelable {
*
* Call this when a field has been detected with a type.
*
- * Altough similiarly named with {@link setFieldClassificationIds},
+ * Altough similiarly named with {@link #setFieldClassificationIds},
* it provides a different functionality - setFieldClassificationIds should
* be used when a field is only suspected to be Autofillable.
* This method should be used when a field is certainly Autofillable
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 954cc960665f..53706cd3f887 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -813,7 +813,7 @@ public final class SaveInfo implements Parcelable {
* If no {@link #Builder(int, AutofillId[]) required ids},
* or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
* were set, Save Dialog will only be triggered if platform detection is enabled, which
- * is indicated when {@link FillRequest.getHints()} is not empty.
+ * is indicated when {@link FillRequest#getHints()} is not empty.
*/
public SaveInfo build() {
throwIfDestroyed();
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index df934335e49d..5d96f69c2929 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -143,7 +143,7 @@ public final class BeginCreateCredentialResponse implements Parcelable {
*
* <p> Note that as a provider service you will only be able to set a remote entry if :
* - Provider service possesses the
- * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+ * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
* - Provider service is configured as the provider that can provide remote entries.
*
* If the above conditions are not met, setting back {@link BeginCreateCredentialResponse}
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index 5ed06ac1ade7..ae6ca25a38ad 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -160,7 +160,7 @@ public final class BeginGetCredentialResponse implements Parcelable {
*
* <p> Note that as a provider service you will only be able to set a remote entry if :
* - Provider service possesses the
- * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+ * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission.
* - Provider service is configured as the provider that can provide remote entries.
*
* If the above conditions are not met, setting back {@link BeginGetCredentialResponse}
diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java
index e755581638f5..c3524c5c85aa 100644
--- a/core/java/android/service/credentials/CallingAppInfo.java
+++ b/core/java/android/service/credentials/CallingAppInfo.java
@@ -103,7 +103,7 @@ public final class CallingAppInfo implements Parcelable {
* of other applications.
*
* Android system makes sure that only applications that poses the permission
- * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
+ * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on
* the incoming {@link android.credentials.GetCredentialRequest} or
* {@link android.credentials.CreateCredentialRequest}.
*/
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 1cfff14b2d7c..759953e4aca4 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -896,8 +896,7 @@ public abstract class NotificationListenerService extends Service {
* <p>This method will throw a security exception if you don't have access to notifications
* for the given user.</p>
* <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
- * device} or be the {@link NotificationAssistantService notification assistant} in order to
- * use this method.
+ * device} or be the notification assistant in order to use this method.
*
* @param pkg The package to retrieve channels for.
*/
@@ -920,8 +919,7 @@ public abstract class NotificationListenerService extends Service {
* <p>This method will throw a security exception if you don't have access to notifications
* for the given user.</p>
* <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
- * device} or be the {@link NotificationAssistantService notification assistant} in order to
- * use this method.
+ * device} or be the notification assistant in order to use this method.
*
* @param pkg The package to retrieve channel groups for.
*/
@@ -2001,7 +1999,7 @@ public abstract class NotificationListenerService extends Service {
/**
* Returns a list of smart {@link Notification.Action} that can be added by the
- * {@link NotificationAssistantService}
+ * notification assistant.
*/
public @NonNull List<Notification.Action> getSmartActions() {
return mSmartActions == null ? Collections.emptyList() : mSmartActions;
@@ -2022,8 +2020,7 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * Returns a list of smart replies that can be added by the
- * {@link NotificationAssistantService}
+ * Returns a list of smart replies that can be added by the notification assistant.
*/
public @NonNull List<CharSequence> getSmartReplies() {
return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index 4a4fd041fb19..bf129c551605 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -372,9 +372,9 @@ public final class WalletCard implements Parcelable {
}
/**
- * Set of locations this card might be useful at. If {@link
- * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
- * shown to the user when a user is near one of these locations.
+ * Set of locations this card might be useful at. If
+ * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is
+ * enabled, the card might be shown to the user when a user is near one of these locations.
*/
@NonNull
public Builder setCardLocations(@NonNull List<Location> cardLocations) {
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index cabcae30851f..d40b39e18edb 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1809,7 +1809,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
* referring to the device that sent the request. Starting from Android 14, the system will
- * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
+ * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
* application should handle null case before Android 14.
* @param showFlags The show flags originally provided to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 8862f1d74ab1..a0cd0748f509 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1274,7 +1274,7 @@ public class DynamicLayout extends Layout {
}
/**
- * Gets the {@link LineBreakconfig} used in this DynamicLayout.
+ * Gets the {@link LineBreakConfig} used in this DynamicLayout.
* Use this only to consult the LineBreakConfig's properties and not
* to change them.
*
diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java
index be002f3102d3..b0a70eae902a 100644
--- a/core/java/android/text/WordSegmentFinder.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -24,7 +24,7 @@ import android.text.method.WordIterator;
/**
* Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
- * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
+ * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
* any text segments.
*
* <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0b2b6ce33666..506945501609 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1366,7 +1367,8 @@ public final class Display {
// form of the larger DISPLAY_CHANGED event
mGlobal.registerDisplayListener(toRegister, executor,
DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
- | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED,
+ ActivityThread.currentPackageName());
}
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514349c3..11180aef4479 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -54,6 +54,10 @@ oneway interface IWindow {
*/
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
+ /**
+ * Please dispatch through WindowStateResizeItem instead of directly calling this method from
+ * the system server.
+ */
void resized(in ClientWindowFrames frames, boolean reportDraw,
in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index c35b6907cd9d..9f886c826174 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -778,7 +778,8 @@ public final class InputDevice implements Parcelable {
* Each gamepad or joystick is given a unique, positive controller number when initially
* configured by the system. This number may change due to events such as device disconnects /
* reconnects or user initiated reassignment. Any change in number will trigger an event that
- * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}.
+ * can be observed by registering an
+ * {@link android.hardware.input.InputManager.InputDeviceListener}.
* </p>
* <p>
* All input devices which are not gamepads or joysticks will be assigned a controller number
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 78716f5646f6..0ba414817247 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -17,7 +17,6 @@
package android.view;
import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
import android.view.flags.Flags;
/**
@@ -43,7 +42,8 @@ import android.view.flags.Flags;
* the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID
* that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}.
* Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an
- * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input
+ * {@link android.hardware.input.InputManager} instance
+ * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input
* device IDs).
*
* <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b080b7115728..2f3d73a36425 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -263,7 +263,7 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,
long nativeObject, int compatibility);
private static native void nativeSetFrameRateCategory(
- long transactionObj, long nativeObject, int category);
+ long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);
private static native void nativeSetFrameRateSelectionStrategy(
long transactionObj, long nativeObject, int strategy);
private static native long nativeGetHandle(long nativeObject);
@@ -3709,6 +3709,10 @@ public final class SurfaceControl implements Parcelable {
* @param sc The SurfaceControl to specify the frame rate category of.
* @param category The frame rate category of this surface. The category value may influence
* the system's choice of display frame rate.
+ * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not
+ * change if changing it would cause jank. Else {@code false}.
+ * This parameter is ignored when {@code category} is
+ * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.
*
* @return This transaction object.
*
@@ -3717,10 +3721,10 @@ public final class SurfaceControl implements Parcelable {
* @hide
*/
@NonNull
- public Transaction setFrameRateCategory(
- @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) {
+ public Transaction setFrameRateCategory(@NonNull SurfaceControl sc,
+ @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {
checkPreconditions(sc);
- nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category);
+ nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);
return this;
}
@@ -4266,8 +4270,7 @@ public final class SurfaceControl implements Parcelable {
* be somewhat arbitrary, and so there are some somewhat arbitrary decisions in
* this API as well.
* <p>
- * @param sc The {@link SurfaceControl} to set the
- * {@link TrustedPresentationCallback} on
+ * @param sc The {@link SurfaceControl} to set the callback on
* @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to
* invoke the callback.
* @param executor The {@link Executor} where the callback will be invoked on.
@@ -4300,10 +4303,9 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl}
+ * Clears the callback for a specific {@link SurfaceControl}
*
- * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be
- * cleared from
+ * @param sc The SurfaceControl that the callback should be cleared from
* @return This transaction
*/
@NonNull
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 16318e0a3f49..e9d0e4c557c6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9308,6 +9308,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
structure.setAutofillType(autofillType);
structure.setAutofillHints(getAutofillHints());
structure.setAutofillValue(getAutofillValue());
+ structure.setIsCredential(isCredential());
}
structure.setImportantForAutofill(getImportantForAutofill());
structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes());
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cf8a5b42a8b9..939278314df4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1549,7 +1549,8 @@ public final class ViewRootImpl implements ViewParent,
mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED
| DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+ mBasePackageName);
}
/**
@@ -10547,6 +10548,8 @@ public final class ViewRootImpl implements ViewParent,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
boolean dragResizing) {
+ // Although this is a AIDL method, it will only be triggered in local process through
+ // either WindowStateResizeItem or WindowlessWindowManager.
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 2c2ae06e9186..bb2c7c8b198b 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -397,6 +397,13 @@ public abstract class ViewStructure {
public void setImportantForAutofill(@AutofillImportance int mode) {}
/**
+ * Sets whether the node is a credential. See {@link View#isCredential}.
+ *
+ * @hide
+ */
+ public void setIsCredential(boolean isCredential) {}
+
+ /**
* Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}.
*
* <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6c8f5424f85e..4f03ce98ccac 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,6 +17,7 @@
package android.view;
import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST;
import static android.view.View.STATUS_BAR_DISABLE_BACK;
import static android.view.View.STATUS_BAR_DISABLE_CLOCK;
import static android.view.View.STATUS_BAR_DISABLE_EXPAND;
@@ -1685,7 +1686,7 @@ public interface WindowManager extends ViewManager {
* orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This
* listener gives application an opportunity to selectively react to device orientation changes.
* The newly added listener will be called with current proposed rotation. Note that the context
- * of this window manager instance must be a {@link android.annotation.UiContext}.
+ * of this window manager instance must be a {@code UiContext}.
*
* @param executor The executor on which callback method will be invoked.
* @param listener Called when the proposed rotation for the context is being delivered.
@@ -1693,7 +1694,7 @@ public interface WindowManager extends ViewManager {
* {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and
* {@link Surface#ROTATION_270}.
* @throws UnsupportedOperationException if this method is called on an instance that is not
- * associated with a {@link android.annotation.UiContext}.
+ * associated with a {@code UiContext}.
*/
default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,
@NonNull IntConsumer listener) {
@@ -3115,7 +3116,7 @@ public interface WindowManager extends ViewManager {
/**
* Never animate position changes of the window.
*
- * @see android.R.attr#Window_windowNoMoveAnimation
+ * @see android.R.styleable#Window_windowNoMoveAnimation
* {@hide}
*/
@UnsupportedAppUsage
@@ -3905,6 +3906,7 @@ public interface WindowManager extends ViewManager {
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
@TestApi
public float preferredMinDisplayRefreshRate;
@@ -3914,6 +3916,7 @@ public interface WindowManager extends ViewManager {
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
@TestApi
public float preferredMaxDisplayRefreshRate;
@@ -4531,7 +4534,7 @@ public interface WindowManager extends ViewManager {
* Set whether animations can be played for position changes on this window. If disabled,
* the window will move to its new position instantly without animating.
*
- * @attr ref android.R.attr#Window_windowNoMoveAnimation
+ * @attr ref android.R.styleable#Window_windowNoMoveAnimation
*/
public void setCanPlayMoveAnimation(boolean enable) {
if (enable) {
@@ -4546,7 +4549,7 @@ public interface WindowManager extends ViewManager {
* This does not guarantee that an animation will be played in all such situations. For
* example, drag-resizing may move the window but not play an animation.
*
- * @attr ref android.R.attr#Window_windowNoMoveAnimation
+ * @attr ref android.R.styleable#Window_windowNoMoveAnimation
*/
public boolean canPlayMoveAnimation() {
return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0;
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 7ad43c76efaa..26298bc645ad 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -47,7 +47,6 @@ import java.util.function.Supplier;
* @see WindowInsets#getInsets(int)
* @see WindowManager#getCurrentWindowMetrics()
* @see WindowManager#getMaximumWindowMetrics()
- * @see android.annotation.UiContext
*/
public final class WindowMetrics {
@NonNull
@@ -99,8 +98,7 @@ public final class WindowMetrics {
}
/**
- * Returns the bounds of the area associated with this window or
- * {@link android.annotation.UiContext}.
+ * Returns the bounds of the area associated with this window or {@code UiContext}.
* <p>
* <b>Note that the size of the reported bounds can have different size than
* {@link Display#getSize(Point)}.</b> This method reports the window size including all system
@@ -133,7 +131,7 @@ public final class WindowMetrics {
/**
* Returns the {@link WindowInsets} of the area associated with this window or
- * {@link android.annotation.UiContext}.
+ * {@code UiContext}.
*
* @return the {@link WindowInsets} of the visual area.
*/
@@ -146,9 +144,8 @@ public final class WindowMetrics {
}
/**
- * Returns the density of the area associated with this window or
- * {@link android.annotation.UiContext}, which uses the same units as
- * {@link android.util.DisplayMetrics#density}.
+ * Returns the density of the area associated with this window or {@code UiContext},
+ * which uses the same units as {@link android.util.DisplayMetrics#density}.
*
* @see android.util.DisplayMetrics#DENSITY_DEFAULT
* @see android.util.DisplayMetrics#density
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 60f46e60ec9e..12ce0f47c460 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -1731,6 +1731,9 @@ public final class AccessibilityInteractionClient
@Override
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId) {
+ if (!Flags.a11yOverlayCallbacks()) {
+ return;
+ }
synchronized (mInstanceLock) {
if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
final Pair<Executor, IntConsumer> pair =
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e6a8b7827b04..43bfe139c223 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1562,9 +1562,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* describes the action.
* </p>
* <p>
- * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
- * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
- * view.
+ * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence,
+ * AccessibilityViewCommand)} to register an action directly on the view.
* <p>
* <strong>Note:</strong> Cannot be called from an
* {@link android.accessibilityservice.AccessibilityService}.
@@ -5167,8 +5166,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* </p>
* <aside class="note">
* <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
- * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
- * view.
+ * CharSequence, AccessibilityViewCommand)} to register an action directly on the view.
* </p>
*/
public static final class AccessibilityAction implements Parcelable {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 09b2f9c1ee15..fa0052cf664a 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -99,7 +99,6 @@ public final class AccessibilityWindowInfo implements Parcelable {
/** @hide */
public static final int UNDEFINED_CONNECTION_ID = -1;
/** @hide */
- @TestApi
public static final int UNDEFINED_WINDOW_ID = -1;
/** @hide */
public static final int ANY_WINDOW_ID = -2;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 85dadd4a061d..cc612ed93b2f 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -13,3 +13,10 @@ flag {
description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
bug: "303871725"
}
+
+flag {
+ name: "a11y_overlay_callbacks"
+ namespace: "accessibility"
+ description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+ bug: "304478691"
+}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index a76780d18f71..32256b9b09c8 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -56,7 +56,7 @@ public class AnimationUtils {
private static final int SEQUENTIALLY = 1;
/**
- * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
+ * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
* this change ID enables to use expectedPresentationTime instead of the frameTime
* for the frame start time .
*
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index dc3d32317ded..bb815c0e8317 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -183,7 +183,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {
public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;
/**
- * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
+ * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps
* the virtual children with a pair of view tree appearing and view tree appeared events.
*/
@ChangeId
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index 7e06f87d931a..5c86feb3c22c 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -6,3 +6,10 @@ flag {
description: "If true, content protection blocklist is mutable and can be updated."
bug: "301658008"
}
+
+flag {
+ name: "parse_groups_config_enabled"
+ namespace: "content_protection"
+ description: "If true, content protection groups config will be parsed."
+ bug: "302187922"
+}
diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java
index 6e3d9a8786af..927874ffc3a2 100644
--- a/core/java/android/view/displayhash/DisplayHashResultCallback.java
+++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java
@@ -106,7 +106,7 @@ public interface DisplayHashResultCallback {
* {@link android.view.View#generateDisplayHash(String, Rect, Executor,
* DisplayHashResultCallback)} results in an error and cannot generate a display hash.
*
- * @param errorCode One of the values in {@link DisplayHashErrorCode}
+ * @param errorCode the error code
*/
void onDisplayHashError(@DisplayHashErrorCode int errorCode);
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 56b5fac1c64a..fd9689013af3 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -26,4 +26,11 @@ flag {
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
bug: "299946220"
+}
+
+flag {
+ name: "wm_display_refresh_rate_test"
+ namespace: "core_graphics"
+ description: "Adds WindowManager display refresh rate fields to test API"
+ bug: "304475199"
} \ No newline at end of file
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a92420a2f373..c5114b9550db 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -47,7 +47,6 @@ import android.view.MotionEvent;
import android.view.MotionEvent.ToolType;
import android.view.View;
import android.view.autofill.AutofillId;
-import android.widget.Editor;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodDebug;
@@ -722,9 +721,9 @@ public class EditorInfo implements InputType, Parcelable {
private boolean mIsStylusHandwritingEnabled;
/**
- * Set {@code true} if the {@link Editor} has
+ * Set {@code true} if the {@code Editor} has
* {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled.
- * {@code false} by default, {@link Editor} must set it {@code true} to indicate that
+ * {@code false} by default, {@code Editor} must set it {@code true} to indicate that
* it supports stylus handwriting.
*
* @param enabled {@code true} if stylus handwriting is enabled.
@@ -736,7 +735,7 @@ public class EditorInfo implements InputType, Parcelable {
}
/**
- * Returns {@code true} when an {@link Editor} has stylus handwriting enabled.
+ * Returns {@code true} when an {@code Editor} has stylus handwriting enabled.
* {@code false} by default.
* @see #setStylusHandwritingEnabled(boolean)
* @see InputMethodManager#isStylusHandwritingAvailable()
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5bb1e9318175..8159af3ddd4a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -100,7 +100,6 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
-import android.widget.Editor;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -2374,16 +2373,16 @@ public final class InputMethodManager {
* Prepares delegation of starting stylus handwriting session to a different editor in same
* or different window than the view on which initial handwriting stroke was detected.
*
- * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+ * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
* its {@link InputConnection} is started. Calling this method starts buffering of stylus
* motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
* point the handwriting session can be started and the buffered stylus motion events will be
* delivered to the IME.
* e.g. Delegation can be used when initial handwriting stroke is
- * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
- * {@link Editor} is on a different window.
+ * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+ * {@code Editor} is on a different window.
*
- * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled
+ * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled
* upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
*
* @param delegatorView the view that receives initial stylus stroke and delegates it to the
@@ -2402,21 +2401,21 @@ public final class InputMethodManager {
* different window in a different package than the view on which initial handwriting stroke
* was detected.
*
- * Delegation can be used to start stylus handwriting session before the {@link Editor} view or
+ * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
* its {@link InputConnection} is started. Calling this method starts buffering of stylus
* motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
* which point the handwriting session can be started and the buffered stylus motion events will
* be delivered to the IME.
* e.g. Delegation can be used when initial handwriting stroke is
- * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual
- * {@link Editor} is on a different window in the given package.
+ * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
+ * {@code Editor} is on a different window in the given package.
*
* <p>Note: If delegator and delegate are in same package use
* {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
*
* @param delegatorView the view that receives initial stylus stroke and delegates it to the
* actual editor. Its window must {@link View#hasWindowFocus have focus}.
- * @param delegatePackageName package name that contains actual {@link Editor} which should
+ * @param delegatePackageName package name that contains actual {@code Editor} which should
* start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
* @see #prepareStylusHandwritingDelegation(View)
* @see #acceptStylusHandwritingDelegation(View, String)
diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java
index 5be0e3f3ccf2..78de7e16edd7 100644
--- a/core/java/android/view/inspector/PropertyReader.java
+++ b/core/java/android/view/inspector/PropertyReader.java
@@ -124,7 +124,7 @@ public interface PropertyReader {
void readObject(int id, @Nullable Object value);
/**
- * Read a color packed into a {@link ColorInt} as a property.
+ * Read a color packed into an int as a property.
*
* @param id Identifier of the property from a {@link PropertyMapper}
* @param value Value of the property
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index f53737abf707..55623608b025 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -49,7 +49,7 @@ public final class BackEvent {
private final int mSwipeEdge;
/**
- * Creates a new {@link BackMotionEvent} instance.
+ * Creates a new {@link BackEvent} instance.
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 5d14698c82b3..eb9d62bb9438 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -263,6 +263,7 @@ public final class SurfaceSyncGroup {
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady");
}
synchronized (mLock) {
+ toggleTimeout(false);
if (mHasWMSync) {
try {
WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken);
diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java
index 07ac2922304e..2736b68845a2 100644
--- a/core/java/android/window/SystemPerformanceHinter.java
+++ b/core/java/android/window/SystemPerformanceHinter.java
@@ -211,7 +211,11 @@ public class SystemPerformanceHinter {
session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH);
+ // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+ // the display mode will cause would jank on non-VRR devices because keeping a lower
+ // refresh rate would mean a poorer user experience.
+ mTransaction.setFrameRateCategory(
+ displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
transactionChanged = true;
Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"
+ session.reason, session.traceCookie);
@@ -251,7 +255,11 @@ public class SystemPerformanceHinter {
session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_SELF);
- mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT);
+ // smoothSwitchOnly is false to request a higher framerate, even if it means switching
+ // the display mode will cause would jank on non-VRR devices because keeping a lower
+ // refresh rate would mean a poorer user experience.
+ mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
+ /* smoothSwitchOnly= */ false);
transactionChanged = true;
Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,
session.traceCookie);
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index d2a16a3a9212..61f340a856c4 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -361,6 +362,15 @@ public final class TransitionInfo implements Parcelable {
}
/**
+ * Whether this transition contains any changes to the window hierarchy,
+ * including keyguard visibility.
+ */
+ public boolean hasChangesOrSideEffects() {
+ return !mChanges.isEmpty() || isKeyguardGoingAway()
+ || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+ }
+
+ /**
* Whether this transition includes keyguard going away.
*/
public boolean isKeyguardGoingAway() {
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 611da3cec5c6..0cbfcc501918 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -155,7 +155,7 @@ public abstract class WindowProviderService extends Service implements WindowPro
}
/**
- * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for
* low memory and trim memory events.
*/
@Override
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7c931cd9fa15..392aa1b13e0e 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -13,3 +13,11 @@ flag {
description: "On close to square display, when necessary, configuration includes status bar"
bug: "291870756"
}
+
+flag {
+ name: "dimmer_refactor"
+ namespace: "windowing_frontend"
+ description: "Refactor dim to fix flickers"
+ bug: "281632483,295291019"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ec5d4ff16ee5..24dc6dbfede8 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -22,3 +22,10 @@ flag {
description: "Whether the TaskFragment system organizer feature is enabled"
bug: "284050041"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "window_state_resize_item_flag"
+ description: "Whether to dispatch window resize through ClientTransaction is enabled"
+ bug: "301870955"
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 58376a77c705..0801dd8c0bd8 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -62,16 +62,14 @@ import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
+ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Deque;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
/**
* A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -89,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider {
DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,
DocumentsContract.QUERY_ARG_MIME_TYPES);
+ private static final int MAX_RESULTS_NUMBER = 23;
+
private static String joinNewline(String... args) {
return TextUtils.join("\n", args);
}
@@ -375,62 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider {
}
/**
- * This method is similar to
- * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns
- * all children documents including hidden directories/files.
- *
- * <p>
- * In a scoped storage world, access to "Android/data" style directories are hidden for privacy
- * reasons. This method may show privacy sensitive data, so its usage should only be in
- * restricted modes.
- *
- * @param parentDocumentId the directory to return children for.
- * @param projection list of {@link Document} columns to put into the
- * cursor. If {@code null} all supported columns should be
- * included.
- * @param sortOrder how to order the rows, formatted as an SQL
- * {@code ORDER BY} clause (excluding the ORDER BY itself).
- * Passing {@code null} will use the default sort order, which
- * may be unordered. This ordering is a hint that can be used to
- * prioritize how data is fetched from the network, but UI may
- * always enforce a specific ordering
- * @throws FileNotFoundException when parent document doesn't exist or query fails
+ * WARNING: this method should really be {@code final}, but for the backward compatibility it's
+ * not; new classes that extend {@link FileSystemProvider} should override
+ * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.
*/
- protected Cursor queryChildDocumentsShowAll(
- String parentDocumentId, String[] projection, String sortOrder)
+ @Override
+ public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
throws FileNotFoundException {
- return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true);
+ return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);
}
+ /**
+ * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it
+ * could return <b>all</b> content of the directory, <b>including restricted (hidden)
+ * directories and files</b>.
+ * <p>
+ * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and
+ * {@code Android/obb/} on the external storage) are hidden for privacy reasons.
+ * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care.
+ */
@Override
- public Cursor queryChildDocuments(
- String parentDocumentId, String[] projection, String sortOrder)
- throws FileNotFoundException {
- // Access to some directories is hidden for privacy reasons.
- return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow);
+ public final Cursor queryChildDocumentsForManage(String documentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+ return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);
}
- private Cursor queryChildDocuments(
- String parentDocumentId, String[] projection, String sortOrder,
- @NonNull Predicate<File> filter) throws FileNotFoundException {
- final File parent = getFileForDocId(parentDocumentId);
+ protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder,
+ boolean includeHidden) throws FileNotFoundException {
+ final File parent = getFileForDocId(documentId);
final MatrixCursor result = new DirectoryCursor(
- resolveProjection(projection), parentDocumentId, parent);
+ resolveProjection(projection), documentId, parent);
+
+ if (!parent.isDirectory()) {
+ Log.w(TAG, '"' + documentId + "\" is not a directory");
+ return result;
+ }
- if (!filter.test(parent)) {
- Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId);
+ if (!includeHidden && shouldHideDocument(documentId)) {
+ Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");
return result;
}
- if (parent.isDirectory()) {
- for (File file : FileUtils.listFilesOrEmpty(parent)) {
- if (filter.test(file)) {
- includeFile(result, null, file);
- }
- }
- } else {
- Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
+ for (File file : FileUtils.listFilesOrEmpty(parent)) {
+ if (!includeHidden && shouldHideDocument(file)) continue;
+
+ includeFile(result, null, file);
}
+
return result;
}
@@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider {
*
* @see ContentResolver#EXTRA_HONORED_ARGS
*/
- protected final Cursor querySearchDocuments(
- File folder, String[] projection, Set<String> exclusion, Bundle queryArgs)
- throws FileNotFoundException {
+ protected final Cursor querySearchDocuments(File folder, String[] projection,
+ Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveProjection(projection));
- final List<File> pending = new ArrayList<>();
- pending.add(folder);
- while (!pending.isEmpty() && result.getCount() < 24) {
- final File file = pending.remove(0);
- if (shouldHide(file)) continue;
+
+ // We'll be a running a BFS here.
+ final Queue<File> pending = new ArrayDeque<>();
+ pending.offer(folder);
+
+ while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) {
+ final File file = pending.poll();
+
+ // Skip hidden documents (both files and directories)
+ if (shouldHideDocument(file)) continue;
if (file.isDirectory()) {
for (File child : FileUtils.listFilesOrEmpty(file)) {
- pending.add(child);
+ pending.offer(child);
}
}
- if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file,
- queryArgs)) {
+
+ if (exclusion.contains(file.getAbsolutePath())) continue;
+
+ if (matchSearchQueryArguments(file, queryArgs)) {
includeFile(result, null, file);
}
}
@@ -612,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider {
final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);
if (flagIndex != -1) {
+ final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);
int flags = 0;
if (file.canWrite()) {
- if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+ flags |= Document.FLAG_SUPPORTS_DELETE;
+ flags |= Document.FLAG_SUPPORTS_RENAME;
+ flags |= Document.FLAG_SUPPORTS_MOVE;
+ if (isDir) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
- flags |= Document.FLAG_SUPPORTS_DELETE;
- flags |= Document.FLAG_SUPPORTS_RENAME;
- flags |= Document.FLAG_SUPPORTS_MOVE;
-
- if (shouldBlockFromTree(docId)) {
- flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
- }
-
} else {
flags |= Document.FLAG_SUPPORTS_WRITE;
- flags |= Document.FLAG_SUPPORTS_DELETE;
- flags |= Document.FLAG_SUPPORTS_RENAME;
- flags |= Document.FLAG_SUPPORTS_MOVE;
}
}
+ if (isDir && shouldBlockDirectoryFromTree(docId)) {
+ flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
+ }
+
if (mimeType.startsWith("image/")) {
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
@@ -664,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider {
return row;
}
- private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
- "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
-
/**
- * In a scoped storage world, access to "Android/data" style directories are
- * hidden for privacy reasons.
+ * Some providers may want to restrict access to certain directories and files,
+ * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for
+ * privacy reasons.
+ * Such providers should override this method.
*/
- protected boolean shouldHide(@NonNull File file) {
- return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+ protected boolean shouldHideDocument(@NonNull String documentId)
+ throws FileNotFoundException {
+ return false;
}
- private boolean shouldShow(@NonNull File file) {
- return !shouldHide(file);
+ /**
+ * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of
+ * a {@link String} {@code documentId}.
+ *
+ * @see #shouldHideDocument(String)
+ */
+ protected final boolean shouldHideDocument(@NonNull File document)
+ throws FileNotFoundException {
+ return shouldHideDocument(getDocIdForFile(document));
}
- protected boolean shouldBlockFromTree(@NonNull String docId) {
+ /**
+ * @return if the directory that should be blocked from being selected when the user launches
+ * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent.
+ *
+ * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
+ */
+ protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+ throws FileNotFoundException {
return false;
}
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 72a1bac28c9f..ca6c54dc0285 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
@@ -147,7 +148,8 @@ public class DisplayResolutionTracker {
public void registerDisplayListener(DisplayManager.DisplayListener listener) {
manager.registerDisplayListener(listener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+ ActivityThread.currentPackageName());
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index d3103f1ed24b..039943098d7a 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -37,7 +37,6 @@ import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -78,7 +77,7 @@ public class BatteryStatsHistory {
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- private static final int VERSION = 209;
+ private static final int VERSION = 210;
private static final String HISTORY_DIR = "battery-history";
private static final String FILE_SUFFIX = ".bh";
@@ -194,10 +193,11 @@ public class BatteryStatsHistory {
private int mNextHistoryTagIdx = 0;
private int mNumHistoryTagChars = 0;
private int mHistoryBufferLastPos = -1;
- private long mLastHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
- private long mHistoryBaseTimeMs;
+ private final MonotonicClock mMonotonicClock;
+ // Monotonic time when we started writing to the history buffer
+ private long mHistoryBufferStartTime;
private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
@@ -307,23 +307,26 @@ public class BatteryStatsHistory {
* @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
*/
public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock) {
this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
- stepDetailsCalculator, clock, new TraceDelegate());
+ stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
initHistoryBuffer();
}
@VisibleForTesting
public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer) {
this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
- clock, tracer, null);
+ clock, monotonicClock, tracer, null);
}
private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer,
BatteryStatsHistory writableHistory) {
mHistoryBuffer = historyBuffer;
mSystemDir = systemDir;
@@ -332,6 +335,7 @@ public class BatteryStatsHistory {
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = tracer;
mClock = clock;
+ mMonotonicClock = monotonicClock;
mWritableHistory = writableHistory;
if (mWritableHistory != null) {
mMutable = false;
@@ -381,16 +385,18 @@ public class BatteryStatsHistory {
}
private BatteryHistoryFile makeBatteryHistoryFile() {
- return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+ return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
}
public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock) {
mMaxHistoryFiles = maxHistoryFiles;
mMaxHistoryBufferSize = maxHistoryBufferSize;
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = new TraceDelegate();
mClock = clock;
+ mMonotonicClock = monotonicClock;
mHistoryBuffer = Parcel.obtain();
mSystemDir = null;
@@ -417,16 +423,16 @@ public class BatteryStatsHistory {
mHistoryBuffer = Parcel.obtain();
mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
+ mMonotonicClock = null;
readFromParcel(parcel, true /* useBlobs */);
}
private void initHistoryBuffer() {
- mHistoryBaseTimeMs = 0;
- mLastHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
mWrittenPowerStatsDescriptors.clear();
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -466,7 +472,7 @@ public class BatteryStatsHistory {
historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
- this);
+ null, this);
}
}
@@ -491,7 +497,7 @@ public class BatteryStatsHistory {
* When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
* create next history file.
*/
- public void startNextFile() {
+ public void startNextFile(long elapsedRealtimeMs) {
if (mMaxHistoryFiles == 0) {
Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
return;
@@ -517,6 +523,7 @@ public class BatteryStatsHistory {
Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
}
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -699,9 +706,12 @@ public class BatteryStatsHistory {
if (mHistoryParcels != null) {
while (mParcelIndex < mHistoryParcels.size()) {
final Parcel p = mHistoryParcels.get(mParcelIndex++);
- if (!skipHead(p)) {
+ if (!verifyVersion(p)) {
continue;
}
+ // skip monotonic time field.
+ p.readLong();
+
final int bufSize = p.readInt();
final int curPos = p.dataPosition();
mCurrentParcelEnd = curPos + bufSize;
@@ -745,24 +755,36 @@ public class BatteryStatsHistory {
}
out.unmarshall(raw, 0, raw.length);
out.setDataPosition(0);
- return skipHead(out);
+ if (!verifyVersion(out)) {
+ return false;
+ }
+ // skip monotonic time field.
+ out.readLong();
+ return true;
}
/**
- * Skip the header part of history parcel.
+ * Verify header part of history parcel.
*
- * @param p history parcel to skip head.
* @return true if version match, false if not.
*/
- private boolean skipHead(Parcel p) {
+ private boolean verifyVersion(Parcel p) {
p.setDataPosition(0);
final int version = p.readInt();
- if (version != VERSION) {
- return false;
- }
- // skip historyBaseTime field.
- p.readLong();
- return true;
+ return version == VERSION;
+ }
+
+ /**
+ * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
+ * buffer.
+ */
+ public long getHistoryBufferStartTime(Parcel p) {
+ int pos = p.dataPosition();
+ p.setDataPosition(0);
+ p.readInt(); // Skip the version field
+ long monotonicTime = p.readLong();
+ p.setDataPosition(pos);
+ return monotonicTime;
}
/**
@@ -1438,7 +1460,8 @@ public class BatteryStatsHistory {
throw new ConcurrentModificationException("Battery history is not writable");
}
- final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+ final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
+ - mHistoryLastWritten.time;
final int diffStates = mHistoryLastWritten.states ^ cur.states;
final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
@@ -1476,7 +1499,9 @@ public class BatteryStatsHistory {
mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
mHistoryBufferLastPos = -1;
- elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+
+ elapsedRealtimeMs -= timeDiffMs;
+
// If the last written history had a wakelock tag, we need to retain it.
// Note that the condition above made sure that we aren't in a case where
// both it and the current history item have a wakelock tag.
@@ -1513,7 +1538,7 @@ public class BatteryStatsHistory {
HistoryItem copy = new HistoryItem();
copy.setTo(cur);
- startNextFile();
+ startNextFile(elapsedRealtimeMs);
// startRecordingHistory will reset mHistoryCur.
startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
@@ -1548,7 +1573,7 @@ public class BatteryStatsHistory {
mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
mHistoryLastLastWritten.setTo(mHistoryLastWritten);
final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
- mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+ mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
Slog.wtf(TAG, "Significantly earlier event written to battery history:"
+ " time=" + mHistoryLastWritten.time
@@ -1556,7 +1581,6 @@ public class BatteryStatsHistory {
}
mHistoryLastWritten.tagsFirstOccurrence = hasTags;
writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
cur.wakelockTag = null;
cur.wakeReasonTag = null;
cur.eventCode = HistoryItem.EVENT_NONE;
@@ -1926,6 +1950,10 @@ public class BatteryStatsHistory {
return;
}
+ // Save the monotonic time first, so that even if the history write below fails,
+ // we still wouldn't end up with overlapping history timelines.
+ mMonotonicClock.write();
+
Parcel p = Parcel.obtain();
try {
final long start = SystemClock.uptimeMillis();
@@ -1951,8 +1979,7 @@ public class BatteryStatsHistory {
return;
}
- final long historyBaseTime = in.readLong();
-
+ mHistoryBufferStartTime = in.readLong();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -1972,39 +1999,11 @@ public class BatteryStatsHistory {
mHistoryBuffer.appendFrom(in, curPos, bufSize);
in.setDataPosition(curPos + bufSize);
}
-
- mHistoryBaseTimeMs = historyBaseTime;
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** NEW mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
-
- if (mHistoryBaseTimeMs > 0) {
- long elapsedRealtimeMs = mClock.elapsedRealtime();
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- }
}
private void writeHistoryBuffer(Parcel out) {
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** WRITING mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- sb.append(" mLastHistoryElapsedRealtimeMs: ");
- TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
out.writeInt(BatteryStatsHistory.VERSION);
- out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+ out.writeLong(mHistoryBufferStartTime);
out.writeInt(mHistoryBuffer.dataSize());
if (DEBUG) {
Slog.i(TAG, "***************** WRITING HISTORY: "
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index a5d2d0fc1a01..6bd5898b1637 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,7 +16,6 @@
package com.android.internal.os;
-import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -34,8 +33,8 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistoryItr";
private final BatteryStatsHistory mBatteryStatsHistory;
- private final @CurrentTimeMillisLong long mStartTimeMs;
- private final @CurrentTimeMillisLong long mEndTimeMs;
+ private final long mStartTimeMs;
+ private final long mEndTimeMs;
private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
@@ -43,10 +42,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
new PowerStats.DescriptorRegistry();
private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
private boolean mNextItemReady;
+ private boolean mTimeInitialized;
- public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
- @CurrentTimeMillisLong long startTimeMs,
- @CurrentTimeMillisLong long endTimeMs) {
+ public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
+ long endTimeMs) {
mBatteryStatsHistory = history;
mStartTimeMs = startTimeMs;
mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
@@ -82,7 +81,12 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
break;
}
- final long lastRealtimeMs = mHistoryItem.time;
+ if (!mTimeInitialized) {
+ mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+ mTimeInitialized = true;
+ }
+
+ final long lastMonotonicTimeMs = mHistoryItem.time;
final long lastWalltimeMs = mHistoryItem.currentTime;
try {
readHistoryDelta(p, mHistoryItem);
@@ -93,12 +97,13 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor
if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
&& mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
&& lastWalltimeMs != 0) {
- mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ mHistoryItem.currentTime =
+ lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
}
- if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+ if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
break;
}
- if (mHistoryItem.currentTime >= mStartTimeMs) {
+ if (mHistoryItem.time >= mStartTimeMs) {
mNextItemReady = true;
return;
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 5ea6ba86da71..1f44b338f3f7 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -191,6 +191,27 @@ public final class LongArrayMultiStateCounter implements Parcelable {
}
/**
+ * Sets the new values for the given state.
+ */
+ public void setValues(int state, long[] values) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ if (values.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + values.length + ", expected: " + mLength);
+ }
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_setValues(mNativeObject, state, container.mNativeObject);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
* Sets the new values. The delta between the previously set values and these values
* is distributed among the state according to the time the object spent in those states
* since the previous call to updateValues.
@@ -317,6 +338,10 @@ public final class LongArrayMultiStateCounter implements Parcelable {
private static native void native_setState(long nativeObject, int state, long timestampMs);
@CriticalNative
+ private static native void native_setValues(long nativeObject, int state,
+ long longArrayContainerNativeObject);
+
+ @CriticalNative
private static native void native_updateValues(long nativeObject,
long longArrayContainerNativeObject, long timestampMs);
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
new file mode 100644
index 000000000000..6f114e34b21c
--- /dev/null
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
+ * on reboot, but keeps going.
+ */
+public class MonotonicClock {
+ private static final String TAG = "MonotonicClock";
+
+ private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
+ private static final String XML_ATTR_TIMESHIFT = "timeshift";
+
+ private final AtomicFile mFile;
+ private final Clock mClock;
+ private long mTimeshift;
+
+ public MonotonicClock(File file) {
+ mFile = new AtomicFile(file);
+ mClock = Clock.SYSTEM_CLOCK;
+ read();
+ }
+
+ public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ mClock = clock;
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
+ * after a device reboot the time keeps increasing.
+ */
+ public long monotonicTime() {
+ return monotonicTime(mClock.elapsedRealtime());
+ }
+
+ /**
+ * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
+ * of being read from the Clock.
+ */
+ public long monotonicTime(long elapsedRealtimeMs) {
+ return mTimeshift + elapsedRealtimeMs;
+ }
+
+ private void read() {
+ if (!mFile.exists()) {
+ return;
+ }
+
+ try {
+ readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ }
+ }
+
+ /**
+ * Saves the timeshift into a file. Call this method just before system shutdown, after
+ * writing the last battery history event.
+ */
+ public void write() {
+ if (mFile == null) {
+ return;
+ }
+
+ mFile.write(out -> {
+ try {
+ writeXml(out, Xml.newBinarySerializer());
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ }
+ });
+ }
+
+ /**
+ * Parses an XML file containing the persistent state of the monotonic clock.
+ */
+ @VisibleForTesting
+ public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ long savedTimeshift = 0;
+ try {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
+ savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
+ }
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the monotonic clock.
+ */
+ @VisibleForTesting
+ public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
+ serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.endDocument();
+ }
+}
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index f971849987dd..dc5055a0881b 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -16,9 +16,17 @@
package com.android.internal.os;
+import android.util.Slog;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -29,15 +37,21 @@ import java.util.Arrays;
* values;
*/
public class MultiStateStats {
+ private static final String TAG = "MultiStateStats";
+
+ private static final String XML_TAG_STATS = "stats";
+
/**
* A set of states, e.g. on-battery, screen-on, procstate. The state values are integers
* from 0 to States.mLabels.length
*/
public static class States {
+ final String mName;
final boolean mTracked;
final String[] mLabels;
- public States(boolean tracked, String... labels) {
+ public States(String name, boolean tracked, String... labels) {
+ mName = name;
this.mTracked = tracked;
this.mLabels = labels;
}
@@ -155,11 +169,28 @@ public class MultiStateStats {
>>> mStateBitFieldShifts[stateIndex];
}
- private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+ int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
| (value << mStateBitFieldShifts[stateIndex]);
}
+ int setStateInComposite(int compositeState, String stateName, String stateLabel) {
+ for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+ States stateConfig = mStates[stateIndex];
+ if (stateConfig.mName.equals(stateName)) {
+ for (int state = 0; state < stateConfig.mLabels.length; state++) {
+ if (stateConfig.mLabels[state].equals(stateLabel)) {
+ return setStateInComposite(compositeState, stateIndex, state);
+ }
+ }
+ Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName);
+ return -1;
+ }
+ }
+ Slog.e(TAG, "Unsupported state: " + stateName);
+ return -1;
+ }
+
/**
* Allocates a new stats container using this Factory's configuration.
*/
@@ -195,6 +226,10 @@ public class MultiStateStats {
}
return serialState;
}
+
+ int getSerialState(int compositeState) {
+ return mCompositeToSerialState[compositeState];
+ }
}
private final Factory mFactory;
@@ -254,6 +289,106 @@ public class MultiStateStats {
}
/**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray);
+ }
+
+ private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex,
+ long[] values) throws IOException {
+ if (stateIndex < states.length) {
+ if (!mFactory.mStates[stateIndex].mTracked) {
+ writeXmlAllStates(serializer, states, stateIndex + 1, values);
+ return;
+ }
+
+ for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
+ states[stateIndex] = i;
+ writeXmlAllStates(serializer, states, stateIndex + 1, values);
+ }
+ return;
+ }
+
+ mCounter.getCounts(values, mFactory.getSerialState(states));
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ serializer.startTag(null, XML_TAG_STATS);
+
+ for (int i = 0; i < states.length; i++) {
+ if (mFactory.mStates[i].mTracked && states[i] != 0) {
+ serializer.attribute(null, mFactory.mStates[i].mName,
+ mFactory.mStates[i].mLabels[states[i]]);
+ }
+ }
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != 0) {
+ serializer.attributeLong(null, "_" + i, values[i]);
+ }
+ }
+ serializer.endTag(null, XML_TAG_STATS);
+ }
+
+ /**
+ * Populates the object with contents in an XML doc. The parser is expected to be
+ * positioned on the opening tag of the corresponding element.
+ */
+ public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ String outerTag = parser.getName();
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(XML_TAG_STATS)) {
+ Arrays.fill(tmpArray, 0);
+ int compositeState = 0;
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ String attributeName = parser.getAttributeName(i);
+ if (attributeName.startsWith("_")) {
+ int index;
+ try {
+ index = Integer.parseInt(attributeName.substring(1));
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Unexpected index syntax: " + attributeName, parser, e);
+ }
+ if (index < 0 || index >= tmpArray.length) {
+ Slog.e(TAG, "State index out of bounds: " + index
+ + " length: " + tmpArray.length);
+ return false;
+ }
+ tmpArray[index] = parser.getAttributeLong(i);
+ } else {
+ String attributeValue = parser.getAttributeValue(i);
+ compositeState = mFactory.setStateInComposite(compositeState,
+ attributeName, attributeValue);
+ if (compositeState == -1) {
+ return false;
+ }
+ }
+ }
+ mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray);
+ }
+ }
+ eventType = parser.next();
+ }
+ return true;
+ }
+
+ /**
* Prints the accumulated stats, one line of every combination of states that has data.
*/
public void dump(PrintWriter pw) {
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index e35b7f184663..996e4244c473 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -5,14 +5,10 @@ per-file *Binder* = file:BINDER_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
-per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
-per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
-per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
per-file *Kernel* = file:/BATTERY_STATS_OWNERS
+per-file *Clock* = file:/BATTERY_STATS_OWNERS
per-file *MultiState* = file:/BATTERY_STATS_OWNERS
per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
+per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 8f66d1f9365c..1130a454c6b0 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -24,9 +24,16 @@ import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
@@ -61,6 +68,13 @@ public final class PowerStats {
* to adjust the algorithm in accordance with the stats available on the device.
*/
public static class Descriptor {
+ public static final String XML_TAG_DESCRIPTOR = "descriptor";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_NAME = "name";
+ private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+ private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
+ private static final String XML_TAG_EXTRAS = "extras";
+
/**
* {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
* to; or a custom power component ID (if the value
@@ -126,7 +140,7 @@ public final class PowerStats {
int firstWord = parcel.readInt();
int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
if (version != PARCEL_FORMAT_VERSION) {
- Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
return null;
}
@@ -155,6 +169,71 @@ public final class PowerStats {
that.extras); // Since the Parcel is now unparceled, do a deep comparison
}
+ /**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_DESCRIPTOR);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ serializer.attribute(null, XML_ATTR_NAME, name);
+ serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+ serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+ try {
+ serializer.startTag(null, XML_TAG_EXTRAS);
+ extras.saveToXml(serializer);
+ serializer.endTag(null, XML_TAG_EXTRAS);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ serializer.endTag(null, XML_TAG_DESCRIPTOR);
+ }
+
+ /**
+ * Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned
+ * on or before the opening "descriptor" tag.
+ */
+ public static Descriptor createFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int powerComponentId = -1;
+ String name = null;
+ int statsArrayLength = 0;
+ int uidStatsArrayLength = 0;
+ PersistableBundle extras = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_DESCRIPTOR:
+ powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
+ name = parser.getAttributeValue(null, XML_ATTR_NAME);
+ statsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_STATS_ARRAY_LENGTH);
+ uidStatsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_UID_STATS_ARRAY_LENGTH);
+ break;
+ case XML_TAG_EXTRAS:
+ extras = PersistableBundle.restoreFromXml(parser);
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ if (powerComponentId == -1) {
+ return null;
+ } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
+ extras);
+ } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
+ return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength,
+ extras);
+ } else {
+ Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
+ return null;
+ }
+ }
+
@Override
public int hashCode() {
return Objects.hash(powerComponentId);
@@ -259,7 +338,7 @@ public final class PowerStats {
Descriptor descriptor = registry.get(powerComponentId);
if (descriptor == null) {
- Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+ Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
return null;
}
PowerStats stats = new PowerStats(descriptor);
@@ -274,7 +353,7 @@ public final class PowerStats {
stats.uidStats.put(uid, uidStats);
}
if (parcel.dataPosition() != endPos) {
- Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ ", actual length: " + (parcel.dataPosition() - startPos));
return null;
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ec525f09fa88..4bb7c33b41e2 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -91,6 +91,8 @@ public enum ProtoLogGroup implements IProtoLogGroup {
WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
"CoreBackPreview"),
WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
+
+ WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5d70d379e0c..50253cf9e457 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1315,7 +1315,16 @@ void AndroidRuntime::exit(int code)
ALOGI("VM exiting with result code %d.", code);
onExit(code);
}
+
+#ifdef __ANDROID_CLANG_COVERAGE__
+ // When compiled with coverage, a function is registered with atexit to call
+ // `__llvm_profile_write_file` when the process exit.
+ // For Clang code coverage to work, call exit instead of _exit to run hooks
+ // registered with atexit.
+ ::exit(code);
+#else
::_exit(code);
+#endif
}
void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index ba644eb0c03e..178c0d0d95be 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -963,10 +963,11 @@ static void nativeSetDefaultFrameRateCompatibility(JNIEnv* env, jclass clazz, jl
}
static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong nativeObject, jint category) {
+ jlong nativeObject, jint category,
+ jboolean smoothSwitchOnly) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
- transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category));
+ transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);
}
static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2181,7 +2182,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetFrameRate },
{"nativeSetDefaultFrameRateCompatibility", "(JJI)V",
(void*)nativeSetDefaultFrameRateCompatibility},
- {"nativeSetFrameRateCategory", "(JJI)V",
+ {"nativeSetFrameRateCategory", "(JJIZ)V",
(void*)nativeSetFrameRateCategory},
{"nativeSetFrameRateSelectionStrategy", "(JJI)V",
(void*)nativeSetFrameRateSelectionStrategy},
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 69202111f74c..1f29735b93a4 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -55,6 +55,15 @@ static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
counter->setState(state, timestamp);
}
+static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
+ battery::LongArrayMultiStateCounter *counter =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ std::vector<uint64_t> *vector =
+ reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+ counter->setValue(state, *vector);
+}
+
static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
jlong timestamp) {
battery::LongArrayMultiStateCounter *counter =
@@ -210,6 +219,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
// @CriticalNative
{"native_setState", "(JIJ)V", (void *)native_setState},
// @CriticalNative
+ {"native_setValues", "(JIJ)V", (void *)native_setValues},
+ // @CriticalNative
{"native_updateValues", "(JJJ)V", (void *)native_updateValues},
// @CriticalNative
{"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ca768ad434f1..88b578ba772b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6122,7 +6122,9 @@
android:protectionLevel="signature|privileged|development|appop|retailDemo" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <!-- @SystemApi @hide Allows trusted system components to report events to UsageStatsManager -->
+ <!-- @SystemApi @hide
+ @FlaggedApi("backstage_power.report_usage_stats_permission")
+ Allows trusted system components to report events to UsageStatsManager -->
<permission android:name="android.permission.REPORT_USAGE_STATS"
android:protectionLevel="signature|module" />
@@ -7235,13 +7237,23 @@
<!-- @SystemApi Required for the privileged assistant apps targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- that receive voice trigger from the trusted hotword detection service.
+ that receive voice trigger from a sandboxed {@link HotwordDetectionService}.
<p>Protection level: signature|privileged|appop
@FlaggedApi("android.permission.flags.voice_activation_permission_apis")
@hide -->
<permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
android:protectionLevel="signature|privileged|appop" />
+ <!-- @SystemApi Required for the privileged assistant apps targeting
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+ that receive training data from a sandboxed {@link HotwordDetectionService} or
+ {@link VisualQueryDetectionService}.
+ <p>Protection level: internal|appop
+ @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
+ android:protectionLevel="internal|appop" />
+
<!-- @SystemApi Allows requesting the framework broadcast the
{@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
@hide -->
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8fb48bcb8947..e42962ce9195 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -31,4 +31,13 @@
is a relatively expensive operation, this throttle period may need to be adjusted for low-power
devices-->
<integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+
+ <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
+ stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
+ <integer name="config_powerStatsAggregationPeriod">14400000</integer>
+
+ <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery
+ history time for every aggregated power stats span that is stored stored in PowerStatsStore.
+ It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) -->
+ <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c1ecb05a6df2..76744ea67589 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5124,6 +5124,8 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
<java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
+ <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
+ <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
new file mode 100644
index 000000000000..c00eb91d752a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
+import android.view.IWindow;
+import android.view.InsetsState;
+import android.window.ClientWindowFrames;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link WindowStateResizeItem}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:WindowStateResizeItemTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowStateResizeItemTest {
+
+ @Mock
+ private ClientTransactionHandler mHandler;
+ @Mock
+ private PendingTransactionActions mPendingActions;
+ @Mock
+ private IWindow mWindow;
+ @Mock
+ private ClientWindowFrames mFrames;
+ @Mock
+ private MergedConfiguration mConfiguration;
+ @Mock
+ private InsetsState mInsetsState;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testExecute() throws RemoteException {
+ final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
+ true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ item.execute(mHandler, mPendingActions);
+
+ verify(mWindow).resized(mFrames,
+ true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */,
+ true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
+ true /* dragResizing */);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 0941a2b6d263..6c14ee382bdc 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest {
);
});
- PollingCheck.waitFor(/* timeout= */ 7000, () -> {
+ PollingCheck.waitFor(/* timeout= */ 10000, () -> {
AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
rule.getScenario().onActivity(activity ->
isActivityAtCorrectScale.set(
@@ -163,7 +163,7 @@ public class FontScaleConverterActivityTest {
});
PollingCheck.waitFor(
- /* timeout= */ 5000,
+ /* timeout= */ 10000,
() -> InstrumentationRegistry.getInstrumentation()
.getContext()
.getResources()
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 149f58f0a69b..c2e6b60cd680 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -42,8 +42,8 @@ import org.mockito.MockitoAnnotations;
public class DisplayManagerGlobalTest {
private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
@Mock
private IDisplayManager mDisplayManager;
@@ -69,7 +69,8 @@ public class DisplayManagerGlobalTest {
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -97,26 +98,27 @@ public class DisplayManagerGlobalTest {
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
int displayId = 1;
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
@@ -139,7 +141,7 @@ public class DisplayManagerGlobalTest {
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
inOrder.verify(mDisplayManager)
@@ -163,6 +165,7 @@ public class DisplayManagerGlobalTest {
}
private void waitForHandler() {
- mHandler.runWithScissors(() -> { }, 0);
+ mHandler.runWithScissors(() -> {
+ }, 0);
}
}
diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
deleted file mode 100644
index e1f9523f1b49..000000000000
--- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os.health;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.ConditionVariable;
-import android.os.PowerMonitor;
-import android.os.PowerMonitorReadings;
-
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SystemHealthManagerTest {
- private List<PowerMonitor> mPowerMonitorInfo;
- private PowerMonitorReadings mReadings;
- private RuntimeException mException;
-
- @Test
- public void getPowerMonitors() {
- SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
- List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors();
- assertThat(powerMonitorInfo).isNotNull();
- if (powerMonitorInfo.isEmpty()) {
- // This device does not support PowerStats HAL
- return;
- }
-
- PowerMonitor consumerMonitor = null;
- PowerMonitor measurementMonitor = null;
- for (PowerMonitor pmi : powerMonitorInfo) {
- if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
- measurementMonitor = pmi;
- } else {
- consumerMonitor = pmi;
- }
- }
-
- List<PowerMonitor> selectedMonitors = new ArrayList<>();
- if (consumerMonitor != null) {
- selectedMonitors.add(consumerMonitor);
- }
- if (measurementMonitor != null) {
- selectedMonitors.add(measurementMonitor);
- }
-
- PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors);
-
- for (PowerMonitor monitor : selectedMonitors) {
- assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0);
- assertThat(readings.getTimestamp(monitor)).isGreaterThan(0);
- }
- }
-
- @Test
- public void getPowerMonitorsAsync() {
- SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class);
- ConditionVariable done = new ConditionVariable();
- shm.getSupportedPowerMonitors(null, pms -> {
- mPowerMonitorInfo = pms;
- done.open();
- });
- done.block();
- assertThat(mPowerMonitorInfo).isNotNull();
- if (mPowerMonitorInfo.isEmpty()) {
- // This device does not support PowerStats HAL
- return;
- }
-
- PowerMonitor consumerMonitor = null;
- PowerMonitor measurementMonitor = null;
- for (PowerMonitor pmi : mPowerMonitorInfo) {
- if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
- measurementMonitor = pmi;
- } else {
- consumerMonitor = pmi;
- }
- }
-
- List<PowerMonitor> selectedMonitors = new ArrayList<>();
- if (consumerMonitor != null) {
- selectedMonitors.add(consumerMonitor);
- }
- if (measurementMonitor != null) {
- selectedMonitors.add(measurementMonitor);
- }
-
- done.close();
- shm.getPowerMonitorReadings(selectedMonitors, null,
- readings -> {
- mReadings = readings;
- done.open();
- },
- exception -> {
- mException = exception;
- done.open();
- }
- );
- done.block();
-
- assertThat(mException).isNull();
-
- for (PowerMonitor monitor : selectedMonitors) {
- assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0);
- assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0);
- }
- }
-}
diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
index 263e563bc224..6229530dc33f 100644
--- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
+++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java
@@ -31,6 +31,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -154,7 +155,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).applyAsyncUnsafe();
}
@@ -171,7 +173,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).applyAsyncUnsafe();
}
@@ -241,7 +244,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -261,7 +265,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -281,7 +286,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -299,7 +305,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -310,7 +317,7 @@ public class SystemPerformanceHinterTests {
mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);
// Verify we never call SF and perf manager since session1 is already running
verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
- verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+ verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
verify(mTransaction, never()).setEarlyWakeupEnd();
verify(mTransaction, never()).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -318,7 +325,7 @@ public class SystemPerformanceHinterTests {
session2.close();
// Verify we have not cleaned up because session1 is still running
verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt());
- verify(mTransaction, never()).setFrameRateCategory(any(), anyInt());
+ verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());
verify(mTransaction, never()).setEarlyWakeupEnd();
verify(mTransaction, never()).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -330,7 +337,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
@@ -348,7 +356,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP));
@@ -363,7 +372,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));
verify(mTransaction).setFrameRateCategory(
eq(mSecondaryDisplayRoot),
- eq(FRAME_RATE_CATEGORY_HIGH));
+ eq(FRAME_RATE_CATEGORY_HIGH),
+ eq(false));
verify(mTransaction, never()).setEarlyWakeupStart();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -378,13 +388,15 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mDefaultDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction, never()).setFrameRateSelectionStrategy(
eq(mSecondaryDisplayRoot),
anyInt());
verify(mTransaction, never()).setFrameRateCategory(
eq(mSecondaryDisplayRoot),
- anyInt());
+ anyInt(),
+ eq(false));
verify(mTransaction, never()).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession, never()).sendHint(anyInt());
@@ -401,7 +413,8 @@ public class SystemPerformanceHinterTests {
eq(FRAME_RATE_SELECTION_STRATEGY_SELF));
verify(mTransaction).setFrameRateCategory(
eq(mSecondaryDisplayRoot),
- eq(FRAME_RATE_CATEGORY_DEFAULT));
+ eq(FRAME_RATE_CATEGORY_DEFAULT),
+ eq(false));
verify(mTransaction).setEarlyWakeupEnd();
verify(mTransaction).applyAsyncUnsafe();
verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 2cbeaa17332c..f846ac50d5fb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -34,6 +34,7 @@ import org.junit.runners.Suite;
KernelSingleUidTimeReaderTest.class,
LongArrayMultiStateCounterTest.class,
LongMultiStateCounterTest.class,
+ MonotonicClockTest.class,
PowerProfileTest.class,
PowerStatsTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
new file mode 100644
index 000000000000..7951270461d7
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MonotonicClockTest {
+ private final MockClock mClock = new MockClock();
+
+ @Test
+ public void persistence() throws IOException {
+ MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ monotonicClock.writeXml(out, Xml.newFastSerializer());
+ String xml = out.toString();
+ assertThat(xml).contains("timeshift=\"1234\"");
+
+ mClock.realtime = 42;
+ MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
+ newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()),
+ Xml.newFastPullParser());
+
+ mClock.realtime = 2000;
+ assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9c6528785584..b71aaf3fc2e6 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1801,6 +1801,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-504637678": {
+ "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"-503656156": {
"message": "Update process config of %s to new config %s",
"level": "VERBOSE",
@@ -3739,6 +3745,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityClientController.java"
},
+ "1309365288": {
+ "message": "Removing dim surface %s on transaction %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"1316533291": {
"message": "State movement: %s from:%s to:%s reason:%s",
"level": "VERBOSE",
@@ -4003,6 +4015,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1620751818": {
+ "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"1621562070": {
"message": " startWCT=%s",
"level": "VERBOSE",
@@ -4560,6 +4578,9 @@
"WM_DEBUG_CONTENT_RECORDING": {
"tag": "WindowManager"
},
+ "WM_DEBUG_DIMMER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_DRAW": {
"tag": "WindowManager"
},
diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp
index 63d1f6d6f2d6..db37a38756d2 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -7,6 +7,12 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
+aconfig_declarations {
+ name: "framework_graphics_flags",
+ package: "com.android.graphics.flags",
+ srcs: ["android/framework_graphics.aconfig"],
+}
+
filegroup {
name: "framework-graphics-nonupdatable-sources",
srcs: [
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
new file mode 100644
index 000000000000..e030dad6bf14
--- /dev/null
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.graphics.flags"
+
+flag {
+ name: "exact_compute_bounds"
+ namespace: "framework_graphics"
+ description: "Add a function without unused exact param for computeBounds."
+ bug: "304478551"
+} \ No newline at end of file
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index b5fb13db4ae4..0a6fb8424094 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,11 +16,14 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.graphics.hwui.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
/**
@@ -125,6 +128,7 @@ public final class Gainmap implements Parcelable {
* Creates a new gainmap using the provided gainmap as the metadata source and the provided
* bitmap as the replacement for the gainmapContents
*/
+ @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)
public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index db1cc446b2d6..9fde0fd6e6ab 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1609,7 +1609,7 @@ public class Paint {
/**
* Returns the color of the shadow layer.
*
- * @return the shadow layer's color encoded as a {@link ColorLong}.
+ * @return the shadow layer's color encoded as a {@code ColorLong}.
* @see #setShadowLayer(float,float,float,int)
* @see #setShadowLayer(float,float,float,long)
* @see Color
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index c9c1b23d874c..deb89faf3419 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,11 +16,14 @@
package android.graphics;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import com.android.graphics.flags.Flags;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -309,6 +312,7 @@ public class Path {
*
* @param bounds Returns the computed bounds of the path's control points.
*/
+ @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS)
public void computeBounds(@NonNull RectF bounds) {
nComputeBounds(mNativePath, bounds);
}
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 4461f39fd006..c2a7a84ad809 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -109,9 +109,8 @@ final class RippleShader extends RuntimeShader {
+ " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
+ " vec2 uv = p * in_resolutionScale;\n"
+ " vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
- + " float turbulence = turbulence(uv, in_turbulencePhase);\n"
- + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha "
- + "* turbulence;\n"
+ + " float turb = turbulence(uv, in_turbulencePhase);\n"
+ + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n"
+ " float fade = min(fadeIn, 1. - fadeOutRipple);\n"
+ " float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade "
+ "* in_color.a;\n"
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index dc2e794a1df4..0e3fb163ef75 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -249,7 +249,7 @@ public class LineBreaker {
* @param useBoundsForWidth True for using bounding box, false for advances.
* @return this builder instance
* @see Layout#getUseBoundsForWidth()
- * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
+ * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
*/
@FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0e198d5c56ec..e6de5978ceb0 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -322,7 +322,7 @@ public final class PixelCopy {
}
/**
- * Returns the {@link CopyResultStatus} of the copy request.
+ * Returns the status of the copy request.
*/
public @CopyResultStatus int getStatus() {
return mStatus;
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 31c2eb2efaed..b7ea04fdfe07 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -128,25 +128,6 @@ public class AndroidKeyStoreMaintenance {
}
/**
- * Queries user state from Keystore 2.0.
- *
- * @param userId - Android user id of the user.
- * @return UserState enum variant as integer if successful or an error
- */
- public static int getState(int userId) {
- StrictMode.noteDiskRead();
- try {
- return getService().getState(userId);
- } catch (ServiceSpecificException e) {
- Log.e(TAG, "getState failed", e);
- return e.errorCode;
- } catch (Exception e) {
- Log.e(TAG, "Can not connect to keystore", e);
- return SYSTEM_ERROR;
- }
- }
-
- /**
* Informs Keystore 2.0 that an off body event was detected.
*/
public static void onDeviceOffBody() {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8045f55f6b4c..11b827117aa3 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,8 +19,6 @@ package android.security;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.StrictMode;
-import android.os.UserHandle;
-import android.security.maintenance.UserState;
/**
* @hide This should not be made public in its present form because it
@@ -37,15 +35,6 @@ public class KeyStore {
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
- // States
- public enum State {
- @UnsupportedAppUsage
- UNLOCKED,
- @UnsupportedAppUsage
- LOCKED,
- UNINITIALIZED
- };
-
private static final KeyStore KEY_STORE = new KeyStore();
@UnsupportedAppUsage
@@ -55,28 +44,6 @@ public class KeyStore {
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public State state(int userId) {
- int userState = AndroidKeyStoreMaintenance.getState(userId);
- switch (userState) {
- case UserState.UNINITIALIZED:
- return KeyStore.State.UNINITIALIZED;
- case UserState.LSKF_UNLOCKED:
- return KeyStore.State.UNLOCKED;
- case UserState.LSKF_LOCKED:
- return KeyStore.State.LOCKED;
- default:
- throw new AssertionError(userState);
- }
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public State state() {
- return state(UserHandle.myUserId());
- }
-
- /** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public byte[] get(String key) {
return null;
}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 253d70465720..5825facee021 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -319,7 +319,7 @@ public class KeyStoreException extends Exception {
/**
* Returns one of the error codes exported by the class.
*
- * @return a public error code, one of the values in {@link PublicErrorCode}.
+ * @return a public error code
*/
@PublicErrorCode
public int getNumericErrorCode() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 06ce37148eaf..8cf869b175ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -87,33 +87,28 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
mTransitions.addHandler(this);
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean containsEmbeddingSplit = false;
- boolean containsNonEmbeddedChange = false;
- final List<TransitionInfo.Change> changes = info.getChanges();
- for (int i = changes.size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = changes.get(i);
- if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- containsNonEmbeddedChange = true;
- } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
+ /** Whether ActivityEmbeddingController should animate this transition. */
+ public boolean shouldAnimate(@NonNull TransitionInfo info) {
+ boolean containsEmbeddingChange = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
+ FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
- containsEmbeddingSplit = true;
+ containsEmbeddingChange = true;
}
}
- if (!containsEmbeddingSplit) {
+ if (!containsEmbeddingChange) {
// Let the system to play the default animation if there is no ActivityEmbedding split
// window. This allows to play the app customized animation when there is no embedding,
// such as the device is in a folded state.
return false;
}
- if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+
+ if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
return false;
}
+
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
if (options != null
// Scene-transition will be handled by app side.
@@ -123,6 +118,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
return false;
}
+ return true;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (!shouldAnimate(info)) return false;
+
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +142,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
mAnimationRunner.cancelAnimationFromMerge();
}
+ /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
+ private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 14a040a40874..a533ca5fa8fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,6 +32,7 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -366,11 +367,12 @@ public abstract class WMShellModule {
KeyguardTransitionHandler keyguardTransitionHandler,
Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
pipTransitionController, recentsTransitionHandler,
keyguardTransitionHandler, desktopTasksController,
- unfoldHandler);
+ unfoldHandler, activityEmbeddingController);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 451e61855943..918a5a4bd53e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -43,6 +45,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -74,6 +77,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private final KeyguardTransitionHandler mKeyguardHandler;
private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
+ private ActivityEmbeddingController mActivityEmbeddingController;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -93,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
/** Recents Transition while in desktop mode. */
static final int TYPE_RECENTS_DURING_DESKTOP = 6;
- /** Fuld/Unfold transition. */
+ /** Fold/Unfold transition. */
static final int TYPE_UNFOLD = 7;
+ /** Enter pip from one of the Activity Embedding windows. */
+ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -150,7 +157,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
Optional<DesktopTasksController> desktopTasksControllerOptional,
- Optional<UnfoldTransitionHandler> unfoldHandler) {
+ Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -170,6 +178,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
}
mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
+ mActivityEmbeddingController = activityEmbeddingController.orElse(null);
}, this);
}
}
@@ -192,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
mPipHandler.augmentRequest(transition, request, out);
mSplitHandler.addEnterOrExitIfNeeded(request, out);
return out;
+ } else if (request.getType() == TRANSIT_PIP
+ && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
+ mActivityEmbeddingController != null)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Got a PiP-enter request from an Activity Embedding split");
+ mActiveTransitions.add(new MixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
+ // Postpone transition splitting to later.
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ return out;
} else if (request.getRemoteTransition() != null
&& TransitionUtil.isOpeningType(request.getType())
&& (request.getTriggerTask() == null
@@ -355,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
+ finishTransaction, finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -400,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
}
}
+ private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP from an Activity Embedding window");
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mixed.mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mixed.mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
+ startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -811,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
} else {
mPipHandler.end();
}
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mPipHandler.end();
if (mixed.mLeftoversHandler != null) {
@@ -851,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 87c438a5b37d..ba0ef20c412e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,13 +19,12 @@ package com.android.wm.shell.transition;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.util.Slog;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
-import java.util.ArrayList;
-
/**
* A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
* as sentinels for fast-forwarding through animations when the screen is off.
@@ -34,30 +33,25 @@ import java.util.ArrayList;
* don't register it like a normal handler.
*/
class SleepHandler implements Transitions.TransitionHandler {
- final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
-
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- mSleepTransitions.remove(transition);
- startTransaction.apply();
- finishCallback.onTransitionFinished(null);
- return true;
+ if (info.hasChangesOrSideEffects()) {
+ Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition");
+ return false;
+ } else {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
}
@Override
@Nullable
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- mSleepTransitions.add(transition);
return new WindowContainerTransaction();
}
-
- @Override
- public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
- @Nullable SurfaceControl.Transaction finishTransaction) {
- mSleepTransitions.remove(transition);
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index b13e9a248575..1df11369a049 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -81,9 +81,7 @@
<option name="shell-timeout" value="6600s"/>
<option name="test-timeout" value="6000s"/>
<option name="hidden-api-checks" value="false"/>
- <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
<option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
- -->
<!-- PerfettoListener related arguments -->
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
<option name="instrumentation-arg"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b9c9049e08c4..da83d4c0a122 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1169,7 +1169,7 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
- public void testEmptyTransitionStillReportsKeyguardGoingAway() {
+ public void testEmptyTransition_withKeyguardGoingAway_plays() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1188,6 +1188,65 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
+ public void testSleepTransition_withKeyguardGoingAway_plays(){
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+ // Make a no-op transition
+ TransitionInfo info = new TransitionInfoBuilder(
+ TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ // If keyguard-going-away flag set, then it shouldn't be aborted.
+ assertEquals(1, mDefaultHandler.activeCount());
+ }
+
+ @Test
+ public void testSleepTransition_withChanges_plays(){
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+ // Make a transition with some changes
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP)
+ .addChange(TRANSIT_OPEN).build();
+ info.setTrack(0);
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ // If there is an actual change, then it shouldn't be aborted.
+ assertEquals(1, mDefaultHandler.activeCount());
+ }
+
+
+ @Test
+ public void testSleepTransition_empty_SyncBySleepHandler() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(transitToken,
+ new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+
+ // Make a no-op transition
+ TransitionInfo info = new TransitionInfoBuilder(
+ TRANSIT_SLEEP, 0x0, true /* noOp */).build();
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
+
+ // If there is nothing to actually play, it should not be offered to handlers.
+ assertEquals(0, mDefaultHandler.activeCount());
+ }
+
+ @Test
public void testMultipleTracks() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index d0d3c5e7ece1..e672b983d509 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -20,3 +20,10 @@ flag {
description: "APIs to help enable animations involving gainmaps"
bug: "296482289"
}
+
+flag {
+ name: "gainmap_constructor_with_metadata"
+ namespace: "core_graphics"
+ description: "APIs to create a new gainmap with a bitmap for metadata."
+ bug: "304478551"
+}
diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt
index 794082124344..c396a2032eed 100644
--- a/libs/hwui/api/current.txt
+++ b/libs/hwui/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.graphics {
public class ColorMatrix {
diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt
index 14191ebcb080..d802177e249b 100644
--- a/libs/hwui/api/module-lib-current.txt
+++ b/libs/hwui/api/module-lib-current.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/libs/hwui/api/module-lib-removed.txt
+++ b/libs/hwui/api/module-lib-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/libs/hwui/api/removed.txt
+++ b/libs/hwui/api/removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt
index 14191ebcb080..d802177e249b 100644
--- a/libs/hwui/api/system-current.txt
+++ b/libs/hwui/api/system-current.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/libs/hwui/api/system-removed.txt
+++ b/libs/hwui/api/system-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 90850d36b612..22c586248705 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -26,6 +26,7 @@
#include <gui/TraceUtils.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
@@ -435,7 +436,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
options.fContextDeleteContext = this;
options.fContextDeleteProc = onGrContextReleased;
- return GrDirectContext::MakeVulkan(backendContext, options);
+ return GrDirectContexts::MakeVulkan(backendContext, options);
}
VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
index 16c3f2ed27d8..2dd67f0c12aa 100644
--- a/location/java/android/location/GnssSignalType.java
+++ b/location/java/android/location/GnssSignalType.java
@@ -34,8 +34,7 @@ public final class GnssSignalType implements Parcelable {
/**
* Creates a {@link GnssSignalType} with a full list of parameters.
*
- * @param constellationType the constellation type as defined in
- * {@link GnssStatus.ConstellationType}
+ * @param constellationType the constellation type
* @param carrierFrequencyHz the carrier frequency in Hz
* @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
*/
@@ -66,7 +65,7 @@ public final class GnssSignalType implements Parcelable {
this.mCodeType = codeType;
}
- /** Returns the {@link GnssStatus.ConstellationType}. */
+ /** Returns the constellation type. */
@GnssStatus.ConstellationType
public int getConstellationType() {
return mConstellationType;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ceb3858eb0b3..b002bbf20c08 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1282,10 +1282,8 @@ public final class AudioFormat implements Parcelable {
* {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},
* {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
* {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}.
- * <p> For a valid {@link AudioTrack} channel position mask,
- * the following conditions apply:
- * <br> (1) at most eight channel positions may be used;
- * <br> (2) right/left pairs should be matched.
+ * <p> For output or {@link AudioTrack}, channel position masks which do not contain
+ * matched left/right pairs are invalid.
* <p> For input or {@link AudioRecord}, the mask should be
* {@link AudioFormat#CHANNEL_IN_MONO} or
* {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8c635807022b..21690904fe42 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -463,7 +463,7 @@ public final class MediaRouter2 {
/**
* Returns the current {@link RouteListingPreference} of the target router.
*
- * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+ * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns
* the last {@link RouteListingPreference} set by the process this router was created for.
*
* @see #setRouteListingPreference(RouteListingPreference)
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
index 3c54d4a0d166..b761afaeaa67 100644
--- a/media/java/android/media/RingtoneV1.java
+++ b/media/java/android/media/RingtoneV1.java
@@ -16,15 +16,14 @@
package android.media;
+import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources.NotFoundException;
import android.media.audiofx.HapticGenerator;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.RemoteException;
import android.os.Trace;
import android.os.VibrationEffect;
@@ -62,6 +61,7 @@ class RingtoneV1 implements Ringtone.ApiInterface {
private final Context mContext;
private final AudioManager mAudioManager;
+ private final Ringtone.Injectables mInjectables;
private VolumeShaper.Configuration mVolumeShaperConfig;
private VolumeShaper mVolumeShaper;
@@ -74,12 +74,10 @@ class RingtoneV1 implements Ringtone.ApiInterface {
private final IRingtonePlayer mRemotePlayer;
private final Binder mRemoteToken;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private MediaPlayer mLocalPlayer;
private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
private HapticGenerator mHapticGenerator;
- @UnsupportedAppUsage
private Uri mUri;
private String mTitle;
@@ -94,10 +92,15 @@ class RingtoneV1 implements Ringtone.ApiInterface {
private boolean mHapticGeneratorEnabled = false;
private final Object mPlaybackSettingsLock = new Object();
- /** {@hide} */
- @UnsupportedAppUsage
+ /** @hide */
public RingtoneV1(Context context, boolean allowRemote) {
+ this(context, new Ringtone.Injectables(), allowRemote);
+ }
+
+ /** @hide */
+ RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) {
mContext = context;
+ mInjectables = injectables;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAllowRemote = allowRemote;
mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
@@ -200,7 +203,7 @@ class RingtoneV1 implements Ringtone.ApiInterface {
}
destroyLocalPlayer();
// try opening uri locally before delegating to remote player
- mLocalPlayer = new MediaPlayer();
+ mLocalPlayer = mInjectables.newMediaPlayer();
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
@@ -240,19 +243,7 @@ class RingtoneV1 implements Ringtone.ApiInterface {
*/
public boolean hasHapticChannels() {
// FIXME: support remote player, or internalize haptic channels support and remove entirely.
- try {
- android.os.Trace.beginSection("Ringtone.hasHapticChannels");
- if (mLocalPlayer != null) {
- for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
- if (trackInfo.hasHapticChannels()) {
- return true;
- }
- }
- }
- } finally {
- android.os.Trace.endSection();
- }
- return false;
+ return mInjectables.hasHapticChannels(mLocalPlayer);
}
/**
@@ -334,7 +325,7 @@ class RingtoneV1 implements Ringtone.ApiInterface {
* @see android.media.audiofx.HapticGenerator#isAvailable()
*/
public boolean setHapticGeneratorEnabled(boolean enabled) {
- if (!HapticGenerator.isAvailable()) {
+ if (!mInjectables.isHapticGeneratorAvailable()) {
return false;
}
synchronized (mPlaybackSettingsLock) {
@@ -362,7 +353,7 @@ class RingtoneV1 implements Ringtone.ApiInterface {
mLocalPlayer.setVolume(mVolume);
mLocalPlayer.setLooping(mIsLooping);
if (mHapticGenerator == null && mHapticGeneratorEnabled) {
- mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+ mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer);
}
if (mHapticGenerator != null) {
mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
@@ -397,7 +388,6 @@ class RingtoneV1 implements Ringtone.ApiInterface {
*
* @hide
*/
- @UnsupportedAppUsage
public void setUri(Uri uri) {
setUri(uri, null);
}
@@ -425,7 +415,6 @@ class RingtoneV1 implements Ringtone.ApiInterface {
}
/** {@hide} */
- @UnsupportedAppUsage
public Uri getUri() {
return mUri;
}
@@ -556,7 +545,7 @@ class RingtoneV1 implements Ringtone.ApiInterface {
Log.e(TAG, "Could not load fallback ringtone");
return false;
}
- mLocalPlayer = new MediaPlayer();
+ mLocalPlayer = mInjectables.newMediaPlayer();
if (afd.getDeclaredLength() < 0) {
mLocalPlayer.setDataSource(afd.getFileDescriptor());
} else {
@@ -594,12 +583,12 @@ class RingtoneV1 implements Ringtone.ApiInterface {
}
public boolean isLocalOnly() {
- return mAllowRemote;
+ return !mAllowRemote;
}
public boolean isUsingRemotePlayer() {
// V2 testing api, but this is the v1 approximation.
- return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
+ return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null);
}
class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9a6ed4dcf08..9ced2a45a6f7 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,7 @@
package android.media.audiopolicy;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,6 +49,7 @@ import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.audio.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -417,6 +419,7 @@ public class AudioPolicy {
* @return {@link AudioManager#SUCCESS} if the update was successful,
* {@link AudioManager#ERROR} otherwise.
*/
+ @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API)
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int updateMixingRules(
@NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index a354f9180e09..c9cfa670cf02 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -27,3 +27,10 @@ flag {
description: "Disables the broadcast receiver that prevents scanning when the screen is off."
bug: "304234628"
}
+
+flag {
+ namespace: "media_solutions"
+ name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
+ description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
+ bug: "293743975"
+}
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 6e2aaabf4b04..bbbe7f683b05 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -38,7 +38,7 @@ import java.util.List;
* of {@link MidiReceiver}s for sending data out the output ports.
*
* Unlike traditional MIDI byte streams, only complete UMPs should be sent.
- * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal.
+ * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal.
*
* <p>To extend this class, you must declare the service in your manifest file with
* an intent filter with the {@link #SERVICE_INTERFACE} action
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index d294601b44cc..80e22477efed 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -156,4 +156,24 @@ interface IMediaProjectionManager {
+ ".permission.MANAGE_MEDIA_PROJECTION)")
void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,
in @nullable IMediaProjection projection);
+
+ /**
+ * Notifies system server that we are handling a particular state during the consent flow.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ * @param state The state that SystemUI is handling during the consent flow.
+ * Must be a valid
+ * state defined in the MediaProjectionState enum.
+ * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+ * Indicates the entry point for requesting the permission. Must be
+ * a valid state defined
+ * in the SessionCreationSource enum.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index e88655894156..7d79a6c266e4 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml
index 132028ce98dc..91c92cc13d1b 100644
--- a/media/tests/MediaFrameworkTest/AndroidTest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidTest.xml
@@ -23,5 +23,6 @@
<option name="package" value="com.android.mediaframeworktest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
+ <option name="isolated-storage" value="false"/>
</test>
</configuration>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 9be7004c5701..30edfa40802b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -44,7 +44,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- addMediaMetadataRetrieverStateUnitTests(suite);
addMediaRecorderStateUnitTests(suite);
addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite);
@@ -70,11 +69,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {
}
// Running all unit tests checking the state machine may be time-consuming.
- private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
- suite.addTestSuite(MediaMetadataRetrieverTest.class);
- }
-
- // Running all unit tests checking the state machine may be time-consuming.
private void addMediaRecorderStateUnitTests(TestSuite suite) {
suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class);
suite.addTestSuite(MediaRecorderResetStateUnitTest.class);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index bdca4744ff74..f70d2d1f8ae7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,26 +16,34 @@
package com.android.mediaframeworktest.unit;
+import static org.junit.Assert.assertTrue;
+
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.mediaframeworktest.MediaNames;
import com.android.mediaframeworktest.MediaProfileReader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.FileOutputStream;
import java.io.IOException;
-public class MediaMetadataRetrieverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaMetadataRetrieverTest {
private static final String TAG = "MediaMetadataRetrieverTest";
// Test album art extraction.
@MediumTest
- public static void testGetEmbeddedPicture() throws Exception {
+ @Test
+ public void testGetEmbeddedPicture() throws Exception {
Log.v(TAG, "testGetEmbeddedPicture starts.");
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean supportWMA = MediaProfileReader.getWMAEnable();
@@ -78,7 +86,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
// Test frame capture
@LargeTest
- public static void testThumbnailCapture() throws Exception {
+ @Test
+ public void testThumbnailCapture() throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean supportWMA = MediaProfileReader.getWMAEnable();
boolean supportWMV = MediaProfileReader.getWMVEnable();
@@ -134,7 +143,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
}
@LargeTest
- public static void testMetadataRetrieval() throws Exception {
+ @Test
+ public void testMetadataRetrieval() throws Exception {
boolean supportWMA = MediaProfileReader.getWMAEnable();
boolean supportWMV = MediaProfileReader.getWMVEnable();
boolean hasFailed = false;
@@ -169,7 +179,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
// If the specified call order and valid media file is used, no exception
// should be thrown.
@MediumTest
- public static void testBasicNormalMethodCallSequence() throws Exception {
+ @Test
+ public void testBasicNormalMethodCallSequence() throws Exception {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
@@ -197,7 +208,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
// If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must
// return null.
@MediumTest
- public static void testBasicAbnormalMethodCallSequence() {
+ @Test
+ public void testBasicAbnormalMethodCallSequence() {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) {
@@ -213,7 +225,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
// Test setDataSource()
@MediumTest
- public static void testSetDataSource() throws IOException {
+ @Test
+ public void testSetDataSource() throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean hasFailed = false;
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
index 55b98c4704b1..8d1e5e3a5bab 100644
--- a/media/tests/ringtone/Android.bp
+++ b/media/tests/ringtone/Android.bp
@@ -9,15 +9,24 @@ android_test {
srcs: ["src/**/*.java"],
libs: [
- "android.test.runner",
"android.test.base",
+ "android.test.mock",
+ "android.test.runner",
],
static_libs: [
- "androidx.test.rules",
- "testng",
+ "androidx.test.ext.junit",
"androidx.test.ext.truth",
+ "androidx.test.rules",
"frameworks-base-testutils",
+ "mockito-target-inline-minus-junit4",
+ "testables",
+ "testng",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
],
test_suites: [
diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS
new file mode 100644
index 000000000000..93b44f4788c5
--- /dev/null
+++ b/media/tests/ringtone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345036
+
+include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
index 3c0c6847f557..2c8daba86d19 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
+++ b/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-package com.android.mediaframeworktest.unit;
+package com.android.media;
import static android.media.Ringtone.MEDIA_SOUND;
import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
import static android.media.Ringtone.MEDIA_VIBRATION;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped;
+
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -53,34 +55,29 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.testing.TestableContext;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.mediaframeworktest.R;
+import com.android.framework.base.media.ringtone.tests.R;
+import com.android.media.testing.RingtoneInjectablesTrackingTestRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.FileNotFoundException;
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
+/**
+ * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}.
+ */
@RunWith(AndroidJUnit4.class)
-public class RingtoneTest {
+public class RingtoneBuilderTest {
private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
@@ -93,11 +90,8 @@ public class RingtoneTest {
private static final VibrationEffect VIBRATION_EFFECT =
VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
- private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
- VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
- @Rule
- public final RingtoneInjectablesTrackingTestRule
+ @Rule public final RingtoneInjectablesTrackingTestRule
mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
@Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
@@ -122,6 +116,7 @@ public class RingtoneTest {
mContext = spy(testContext);
}
+
@Test
public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
@@ -142,14 +137,14 @@ public class RingtoneTest {
assertThat(ringtone.isLocalOnly()).isFalse();
// Prepare
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Verify dynamic controls.
ringtone.setVolume(0.8f);
@@ -165,7 +160,7 @@ public class RingtoneTest {
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
// playback case. This shouldn't be necessary in other tests that have the same basic
@@ -199,16 +194,16 @@ public class RingtoneTest {
assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
// Prepare
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verifyZeroInteractions(mMockRemotePlayer);
verifyZeroInteractions(mMockVibrator);
@@ -220,8 +215,8 @@ public class RingtoneTest {
setupFileNotFound(mockMediaPlayer, SOUND_URI);
Ringtone ringtone =
newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .build();
+ .setUri(SOUND_URI)
+ .build();
assertThat(ringtone).isNotNull();
assertThat(ringtone.isUsingRemotePlayer()).isTrue();
@@ -284,7 +279,7 @@ public class RingtoneTest {
// Prepare
// Uses attributes with haptic channels enabled, but will use the effect when there aren't
// any present.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -292,7 +287,7 @@ public class RingtoneTest {
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
// Verify dynamic controls.
@@ -310,7 +305,7 @@ public class RingtoneTest {
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
@@ -388,7 +383,7 @@ public class RingtoneTest {
// Prepare
// Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
// knows there aren't any.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -443,7 +438,7 @@ public class RingtoneTest {
// Prepare
// Uses attributes with haptic channels enabled, but will use the effect when there aren't
// any present.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -451,7 +446,7 @@ public class RingtoneTest {
// Play
ringtone.play();
// Vibrator.vibrate isn't called because the vibration comes from the sound.
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Verify dynamic controls (no-op without sound)
ringtone.setVolume(0.8f);
@@ -466,7 +461,7 @@ public class RingtoneTest {
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
// playback case. This shouldn't be necessary in other tests that have the same basic
@@ -496,17 +491,17 @@ public class RingtoneTest {
// Prepare
// The attributes here have haptic channels enabled (unlike above)
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
when(mockMediaPlayer.isPlaying()).thenReturn(true);
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verifyZeroInteractions(mMockRemotePlayer);
// Nothing after the initial hasVibrator - it uses audio-coupled.
@@ -536,7 +531,7 @@ public class RingtoneTest {
// Prepare
// The attributes here have haptic channels enabled (unlike above)
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).prepare();
// Play
@@ -559,7 +554,7 @@ public class RingtoneTest {
@Test
public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
AssetFileDescriptor testResourceFd =
- mContext.getResources().openRawResourceFd(R.raw.shortmp3);
+ mContext.getResources().openRawResourceFd(R.raw.test_sound_file);
// Ensure it will flow as expected.
assertThat(testResourceFd).isNotNull();
assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
@@ -575,18 +570,18 @@ public class RingtoneTest {
// Delegates straight to fallback in local player.
// Prepare
- verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
+ verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verifyNoMoreInteractions(mockMediaPlayer);
verifyNoMoreInteractions(mMockRemotePlayer);
@@ -615,24 +610,10 @@ public class RingtoneTest {
verifyNoMoreInteractions(mMockRemotePlayer);
}
- @Test
- public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
- mContext.getOrCreateTestableResources()
- .addOverride(com.android.internal.R.raw.fallbackring, null);
- Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(null)
- .setLocalOnly()
- .build();
- // Local player fallback fails as the resource isn't found (no media player creation is
- // attempted), and since there is no local player, the ringtone ends up having nothing to
- // do.
- assertThat(ringtone).isNull();
- }
-
private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
AudioAttributes audioAttributes) {
return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
- .setInjectables(mMediaPlayerRule.injectables);
+ .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables());
}
private static AudioAttributes audioAttributes(int audioUsage) {
@@ -647,194 +628,4 @@ public class RingtoneTest {
doThrow(new FileNotFoundException("Fake file not found"))
.when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
}
-
- private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
- AudioAttributes expectedAudioAttributes) throws Exception {
- verify(mockPlayer).setDataSource(mContext, expectedUri);
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
- AudioAttributes expectedAudioAttributes) throws Exception {
- // This is very specific but it's a simple way to test that the test resource matches.
- if (afd.getDeclaredLength() < 0) {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor());
- } else {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
- }
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).setOnCompletionListener(any());
- verify(mockMediaPlayer).start();
- }
-
- private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).stop();
- verify(mockMediaPlayer).setOnCompletionListener(isNull());
- verify(mockMediaPlayer).reset();
- verify(mockMediaPlayer).release();
- }
-
- /**
- * This rule ensures that all expected media player creations from the factory do actually
- * occur. The reason for this level of control is that creating a media player is fairly
- * expensive and blocking, so we do want unit tests of this class to "declare" interactions
- * of all created media players.
- *
- * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
- * failed (and media player assertions may just be a distracting side effect). Otherwise, the
- * teardown failures hide the real test ones.
- */
- public static class RingtoneInjectablesTrackingTestRule implements TestRule {
- public Ringtone.Injectables injectables = new TestInjectables();
- public boolean hapticGeneratorAvailable = true;
-
- // Queue of (local) media players, in order of expected creation. Enqueue using
- // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
- // This queue is asserted to be empty at the end of the test.
- private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
- // Similar to media players, but for haptic generator, which also needs releasing.
- private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
- // Media players with haptic channels.
- private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- base.evaluate();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
- .that(mMockMediaPlayerQueue).isEmpty();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage(
- "Test setup an expectLocalHapticGenerator but it wasn't consumed")
- .that(mMockHapticGeneratorMap).isEmpty();
- }
- };
- }
-
- private TestMediaPlayer expectLocalMediaPlayer() {
- TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
- // Delegate to simulated methods. This means they can be verified but also reflect
- // realistic transitions from the TestMediaPlayer.
- doCallRealMethod().when(mockMediaPlayer).start();
- doCallRealMethod().when(mockMediaPlayer).stop();
- doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- mMockMediaPlayerQueue.add(mockMediaPlayer);
- return mockMediaPlayer;
- }
-
- private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
- HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
- // A test should never want this.
- assertWithMessage("Can't expect a second haptic generator created "
- + "for one media player")
- .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
- .isNull();
- return mockHapticGenerator;
- }
-
- private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
- if (hasHapticChannels) {
- mHapticChannels.add(mp);
- } else {
- mHapticChannels.remove(mp);
- }
- }
-
- private class TestInjectables extends Ringtone.Injectables {
- @Override
- public MediaPlayer newMediaPlayer() {
- assertWithMessage(
- "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
- .that(mMockMediaPlayerQueue)
- .isNotEmpty();
- return mMockMediaPlayerQueue.remove();
- }
-
- @Override
- public boolean isHapticGeneratorAvailable() {
- return hapticGeneratorAvailable;
- }
-
- @Override
- public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
- HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
- assertWithMessage("Unexpected HapticGenerator creation. "
- + "Bug or need expectHapticGenerator")
- .that(mockHapticGenerator)
- .isNotNull();
- return mockHapticGenerator;
- }
-
- @Override
- public boolean isHapticPlaybackSupported() {
- return true;
- }
-
- @Override
- public boolean hasHapticChannels(MediaPlayer mp) {
- return mHapticChannels.contains(mp);
- }
- }
- }
-
- /**
- * MediaPlayer relies on a native backend and so its necessary to intercept calls from
- * fake usage hitting them.
- *
- * Mocks don't work directly on native calls, but if they're overridden then it does work.
- * Some basic state faking is also done to make the mocks more realistic.
- */
- private static class TestMediaPlayer extends MediaPlayer {
- private boolean mIsPlaying = false;
- private boolean mIsLooping = false;
-
- @Override
- public void start() {
- mIsPlaying = true;
- }
-
- @Override
- public void stop() {
- mIsPlaying = false;
- }
-
- @Override
- public void setLooping(boolean value) {
- mIsLooping = value;
- }
-
- @Override
- public boolean isLooping() {
- return mIsLooping;
- }
-
- @Override
- public boolean isPlaying() {
- return mIsPlaying;
- }
-
- void simulatePlayingFinished() {
- if (!mIsPlaying) {
- throw new IllegalStateException(
- "Attempted to pretend playing finished when not playing");
- }
- mIsPlaying = false;
- }
- }
}
diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
new file mode 100644
index 000000000000..e97e1173a1ea
--- /dev/null
+++ b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.testing;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.net.Uri;
+
+/**
+ * Helper class with assertion methods on mock {@link MediaPlayer} instances.
+ */
+public final class MediaPlayerTestHelper {
+
+ /** Verify this local media player mock instance was started. */
+ public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) {
+ verify(mockMediaPlayer).setOnCompletionListener(any());
+ verify(mockMediaPlayer).start();
+ }
+
+ /** Verify this local media player mock instance was stopped and released. */
+ public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) {
+ verify(mockMediaPlayer).stop();
+ verify(mockMediaPlayer).setOnCompletionListener(isNull());
+ verify(mockMediaPlayer).reset();
+ verify(mockMediaPlayer).release();
+ }
+
+ /** Verify this local media player mock instance was setup with given attributes. */
+ public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer,
+ Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception {
+ verify(mockPlayer).setDataSource(context, expectedUri);
+ verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+ verify(mockPlayer).setPreferredDevice(null);
+ verify(mockPlayer).prepare();
+ }
+
+ /** Verify this local media player mock instance was setup with given fallback attributes. */
+ public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer,
+ AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception {
+ // This is very specific but it's a simple way to test that the test resource matches.
+ if (afd.getDeclaredLength() < 0) {
+ verify(mockPlayer).setDataSource(afd.getFileDescriptor());
+ } else {
+ verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength());
+ }
+ verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+ verify(mockPlayer).setPreferredDevice(null);
+ verify(mockPlayer).prepare();
+ }
+
+ private MediaPlayerTestHelper() {
+ }
+}
diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
new file mode 100644
index 000000000000..25752ce83e5c
--- /dev/null
+++ b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.testing;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.when;
+
+import android.media.MediaPlayer;
+import android.media.Ringtone;
+import android.media.audiofx.HapticGenerator;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+
+import java.util.ArrayDeque;
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * This rule ensures that all expected media player creations from the factory do actually
+ * occur. The reason for this level of control is that creating a media player is fairly
+ * expensive and blocking, so we do want unit tests of this class to "declare" interactions
+ * of all created media players.
+ * <p>
+ * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
+ * failed (and media player assertions may just be a distracting side effect). Otherwise, the
+ * teardown failures hide the real test ones.
+ */
+public class RingtoneInjectablesTrackingTestRule implements TestRule {
+
+ private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables();
+
+ // Queue of (local) media players, in order of expected creation. Enqueue using
+ // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
+ // This queue is asserted to be empty at the end of the test.
+ private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
+
+ // Similar to media players, but for haptic generator, which also needs releasing.
+ private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
+
+ // Media players with haptic channels.
+ private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
+
+ private boolean mHapticGeneratorAvailable = true;
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ // Only assert if the test didn't fail (base.evaluate() would throw).
+ assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
+ .that(mMockMediaPlayerQueue).isEmpty();
+ // Only assert if the test didn't fail (base.evaluate() would throw).
+ assertWithMessage(
+ "Test setup an expectLocalHapticGenerator but it wasn't consumed")
+ .that(mMockHapticGeneratorMap).isEmpty();
+ }
+ };
+ }
+
+ /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */
+ public Ringtone.Injectables getRingtoneTestInjectables() {
+ return mRingtoneTestInjectables;
+ }
+
+ /**
+ * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance
+ * created with {@link #getRingtoneTestInjectables()}.
+ *
+ * <p>If a media player is not created during the test execution after this method is called
+ * then the test will fail. It will also fail if the ringtone attempts to create one without
+ * this method being called first.
+ */
+ public TestMediaPlayer expectLocalMediaPlayer() {
+ TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
+ // Delegate to simulated methods. This means they can be verified but also reflect
+ // realistic transitions from the TestMediaPlayer.
+ doCallRealMethod().when(mockMediaPlayer).start();
+ doCallRealMethod().when(mockMediaPlayer).stop();
+ doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
+ when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+ mMockMediaPlayerQueue.add(mockMediaPlayer);
+ return mockMediaPlayer;
+ }
+
+ /**
+ * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance
+ * created with {@link #getRingtoneTestInjectables()}.
+ *
+ * <p>If a haptic generator is not created during the test execution after this method is called
+ * then the test will fail. It will also fail if the ringtone attempts to create one without
+ * this method being called first.
+ */
+ public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) {
+ HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
+ // A test should never want this.
+ assertWithMessage("Can't expect a second haptic generator created "
+ + "for one media player")
+ .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator))
+ .isNull();
+ return mockHapticGenerator;
+ }
+
+ /**
+ * Configures the {@link MediaPlayer} to always return given flag when
+ * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called.
+ */
+ public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
+ if (hasHapticChannels) {
+ mHapticChannels.add(mp);
+ } else {
+ mHapticChannels.remove(mp);
+ }
+ }
+
+ /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */
+ private class TestInjectables extends Ringtone.Injectables {
+ @Override
+ public MediaPlayer newMediaPlayer() {
+ assertWithMessage(
+ "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
+ .that(mMockMediaPlayerQueue)
+ .isNotEmpty();
+ return mMockMediaPlayerQueue.remove();
+ }
+
+ @Override
+ public boolean isHapticGeneratorAvailable() {
+ return mHapticGeneratorAvailable;
+ }
+
+ @Override
+ public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
+ HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
+ assertWithMessage("Unexpected HapticGenerator creation. "
+ + "Bug or need expectHapticGenerator")
+ .that(mockHapticGenerator)
+ .isNotNull();
+ return mockHapticGenerator;
+ }
+
+ @Override
+ public boolean isHapticPlaybackSupported() {
+ return true;
+ }
+
+ @Override
+ public boolean hasHapticChannels(MediaPlayer mp) {
+ return mHapticChannels.contains(mp);
+ }
+ }
+
+ /**
+ * MediaPlayer relies on a native backend and so its necessary to intercept calls from
+ * fake usage hitting them.
+ * <p>
+ * Mocks don't work directly on native calls, but if they're overridden then it does work.
+ * Some basic state faking is also done to make the mocks more realistic.
+ */
+ public static class TestMediaPlayer extends MediaPlayer {
+ private boolean mIsPlaying = false;
+ private boolean mIsLooping = false;
+
+ @Override
+ public void start() {
+ mIsPlaying = true;
+ }
+
+ @Override
+ public void stop() {
+ mIsPlaying = false;
+ }
+
+ @Override
+ public void setLooping(boolean value) {
+ mIsLooping = value;
+ }
+
+ @Override
+ public boolean isLooping() {
+ return mIsLooping;
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mIsPlaying;
+ }
+
+ /**
+ * Updates {@link #isPlaying()} result to false, if it's set to true.
+ *
+ * @throws IllegalStateException is {@link #isPlaying()} is already false
+ */
+ public void simulatePlayingFinished() {
+ if (!mIsPlaying) {
+ throw new IllegalStateException(
+ "Attempted to pretend playing finished when not playing");
+ }
+ mIsPlaying = false;
+ }
+ }
+}
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 58bcd1d49c69..e71597a27a39 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,11 @@ aidl_interface {
backend: {
java: {
sdk_version: "module_current",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
+
},
rust: {
enabled: true,
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 4c313b22f71e..3409c29d3c2c 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,8 @@
package com.android.externalstorage;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.StorageStatsManager;
@@ -64,7 +66,19 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
-
+import java.util.regex.Pattern;
+
+/**
+ * Presents content of the shared (a.k.a. "external") storage.
+ * <p>
+ * Starting with Android 11 (R), restricts access to the certain sections of the shared storage:
+ * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in
+ * the DocumentsUI by default.
+ * See <a href="https://developer.android.com/about/versions/11/privacy/storage">
+ * Storage updates in Android 11</a>.
+ * <p>
+ * Documents ID format: {@code root:path/to/file}.
+ */
public class ExternalStorageProvider extends FileSystemProvider {
private static final String TAG = "ExternalStorage";
@@ -75,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider {
private static final Uri BASE_URI =
new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();
- // docId format: root:path/to/file
+ /**
+ * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and
+ * {@code /Android/sandbox/} along with all their subdirectories and content.
+ */
+ private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES =
+ Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
@@ -278,76 +297,91 @@ public class ExternalStorageProvider extends FileSystemProvider {
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
+ /**
+ * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the
+ * integrated shared ("external") storage along with all their content and subdirectories as
+ * hidden.
+ */
@Override
- public Cursor queryChildDocumentsForManage(
- String parentDocId, String[] projection, String sortOrder)
- throws FileNotFoundException {
- return queryChildDocumentsShowAll(parentDocId, projection, sortOrder);
+ protected boolean shouldHideDocument(@NonNull String documentId) {
+ // Don't need to hide anything on USB drives.
+ if (isOnRemovableUsbStorage(documentId)) {
+ return false;
+ }
+
+ final String path = getPathFromDocId(documentId);
+ return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();
}
/**
* Check that the directory is the root of storage or blocked file from tree.
+ * <p>
+ * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear
+ * the UI, but the user <b>WILL NOT</b> be able to select them.
*
- * @param docId the docId of the directory to be checked
+ * @param documentId the docId of the directory to be checked
* @return true, should be blocked from tree. Otherwise, false.
+ *
+ * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
*/
@Override
- protected boolean shouldBlockFromTree(@NonNull String docId) {
- try {
- final File dir = getFileForDocId(docId, false /* visible */);
-
- // the file is null or it is not a directory
- if (dir == null || !dir.isDirectory()) {
- return false;
- }
+ protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId)
+ throws FileNotFoundException {
+ final File dir = getFileForDocId(documentId, false);
+ // The file is null or it is not a directory
+ if (dir == null || !dir.isDirectory()) {
+ return false;
+ }
- // Allow all directories on USB, including the root.
- try {
- RootInfo rootInfo = getRootFromDocId(docId);
- if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) {
- return false;
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Failed to determine rootInfo for docId");
- }
+ // Allow all directories on USB, including the root.
+ if (isOnRemovableUsbStorage(documentId)) {
+ return false;
+ }
- final String path = getPathFromDocId(docId);
+ // Get canonical(!) path. Note that this path will have neither leading nor training "/".
+ // This the root's path will be just an empty string.
+ final String path = getPathFromDocId(documentId);
- // Block the root of the storage
- if (path.isEmpty()) {
- return true;
- }
+ // Block the root of the storage
+ if (path.isEmpty()) {
+ return true;
+ }
- // Block Download folder from tree
- if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT),
- path.toLowerCase(Locale.ROOT))) {
- return true;
- }
+ // Block /Download/ and /Android/ folders from the tree.
+ if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) ||
+ equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) {
+ return true;
+ }
- // Block /Android
- if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT),
- path.toLowerCase(Locale.ROOT))) {
- return true;
- }
+ // This shouldn't really make a difference, but just in case - let's block hidden
+ // directories as well.
+ if (shouldHideDocument(documentId)) {
+ return true;
+ }
- // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs
- if (shouldHide(dir)) {
- return true;
- }
+ return false;
+ }
+ private boolean isOnRemovableUsbStorage(@NonNull String documentId) {
+ final RootInfo rootInfo;
+ try {
+ rootInfo = getRootFromDocId(documentId);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');
return false;
- } catch (IOException e) {
- throw new IllegalArgumentException(
- "Failed to determine if " + docId + " should block from tree " + ": " + e);
}
+
+ return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;
}
+ @NonNull
@Override
- protected String getDocIdForFile(File file) throws FileNotFoundException {
+ protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {
return getDocIdForFileMaybeCreate(file, false);
}
- private String getDocIdForFileMaybeCreate(File file, boolean createNewDir)
+ @NonNull
+ private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)
throws FileNotFoundException {
String path = file.getAbsolutePath();
@@ -417,31 +451,33 @@ public class ExternalStorageProvider extends FileSystemProvider {
private File getFileForDocId(String docId, boolean visible, boolean mustExist)
throws FileNotFoundException {
RootInfo root = getRootFromDocId(docId);
- return buildFile(root, docId, visible, mustExist);
+ return buildFile(root, docId, mustExist);
}
- private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
- throws FileNotFoundException {
+ private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {
RootInfo root = getRootFromDocId(docId);
- return Pair.create(root, buildFile(root, docId, visible, true));
+ return Pair.create(root, buildFile(root, docId, /* mustExist */ true));
}
@VisibleForTesting
- static String getPathFromDocId(String docId) throws IOException {
+ static String getPathFromDocId(String docId) {
final int splitIndex = docId.indexOf(':', 1);
final String docIdPath = docId.substring(splitIndex + 1);
- // Get CanonicalPath and remove the first "/"
- final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1);
- if (canonicalPath.isEmpty()) {
- return canonicalPath;
+ // Canonicalize path and strip the leading "/"
+ final String path;
+ try {
+ path = new File(docIdPath).getCanonicalPath().substring(1);
+ } catch (IOException e) {
+ Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"');
+ return "";
}
- // remove trailing "/"
- if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') {
- return canonicalPath.substring(0, canonicalPath.length() - 1);
+ // Remove the trailing "/" as well.
+ if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') {
+ return path.substring(0, path.length() - 1);
} else {
- return canonicalPath;
+ return path;
}
}
@@ -460,7 +496,7 @@ public class ExternalStorageProvider extends FileSystemProvider {
return root;
}
- private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
+ private File buildFile(RootInfo root, String docId, boolean mustExist)
throws FileNotFoundException {
final int splitIndex = docId.indexOf(':', 1);
final String path = docId.substring(splitIndex + 1);
@@ -544,7 +580,7 @@ public class ExternalStorageProvider extends FileSystemProvider {
@Override
public Path findDocumentPath(@Nullable String parentDocId, String childDocId)
throws FileNotFoundException {
- final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false);
+ final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);
final RootInfo root = resolvedDocId.first;
File child = resolvedDocId.second;
@@ -648,6 +684,13 @@ public class ExternalStorageProvider extends FileSystemProvider {
}
}
+ /**
+ * Print the state into the given stream.
+ * Gets invoked when you run:
+ * <pre>
+ * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider
+ * </pre>
+ */
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160);
@@ -731,4 +774,8 @@ public class ExternalStorageProvider extends FileSystemProvider {
}
return bundle;
}
+
+ private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) {
+ return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT));
+ }
}
diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
index 18a8edc5e447..0144b6ea9040 100644
--- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
+++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
@@ -16,47 +16,64 @@
package com.android.externalstorage;
+import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
+
import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY;
import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.Instrumentation;
+import android.content.Context;
import android.content.pm.ProviderInfo;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ExternalStorageProviderTest {
+
+ @NonNull
+ private static final Instrumentation sInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ @NonNull
+ private static final Context sTargetContext = sInstrumentation.getTargetContext();
+
+ private ExternalStorageProvider mExternalStorageProvider;
+
+ @Before
+ public void setUp() {
+ mExternalStorageProvider = new ExternalStorageProvider();
+ }
+
+
@Test
- public void onCreate_shouldUpdateVolumes() throws Exception {
- ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider();
- ExternalStorageProvider spyProvider = spy(externalStorageProvider);
- ProviderInfo providerInfo = new ProviderInfo();
+ public void onCreate_shouldUpdateVolumes() {
+ final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider);
+
+ final ProviderInfo providerInfo = new ProviderInfo();
providerInfo.authority = AUTHORITY;
providerInfo.grantUriPermissions = true;
providerInfo.exported = true;
- InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- spyProvider.attachInfoForTesting(
- InstrumentationRegistry.getTargetContext(), providerInfo);
- }
- });
+ sInstrumentation.runOnMainSync(() ->
+ spyProvider.attachInfoForTesting(sTargetContext, providerInfo));
verify(spyProvider, atLeast(1)).updateVolumes();
}
@Test
- public void testGetPathFromDocId() throws Exception {
+ public void test_getPathFromDocId() {
final String root = "root";
final String path = "abc/def/ghi";
String docId = root + ":" + path;
@@ -79,4 +96,62 @@ public class ExternalStorageProviderTest {
docId = root + ":" + twoDotPath;
assertEquals(getPathFromDocId(docId), path);
}
+
+ @Test
+ public void test_shouldHideDocument() {
+ // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their
+ // "subtrees".
+ final String[] shouldHide = {
+ // "Android/data" and all its subdirectories
+ "Android/data",
+ "Android/data/com.my.app",
+ "Android/data/com.my.app/cache",
+ "Android/data/com.my.app/cache/image.png",
+ "Android/data/mydata",
+
+ // "Android/obb" and all its subdirectories
+ "Android/obb",
+ "Android/obb/com.my.app",
+ "Android/obb/com.my.app/file.blob",
+
+ // "Android/sandbox" and all its subdirectories
+ "Android/sandbox",
+ "Android/sandbox/com.my.app",
+
+ // Also make sure we are not allowing path traversals
+ "Android/./data",
+ "Android/Download/../data",
+ };
+ for (String path : shouldHide) {
+ final String docId = buildDocId(path);
+ assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't",
+ mExternalStorageProvider.shouldHideDocument(docId));
+ }
+
+ // Should NOT hide anything else.
+ final String[] shouldNotHide = {
+ "Android",
+ "Android/datadir",
+ "Documents",
+ "Download",
+ "Music",
+ "Pictures",
+ };
+ for (String path : shouldNotHide) {
+ final String docId = buildDocId(path);
+ assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did",
+ mExternalStorageProvider.shouldHideDocument(docId));
+ }
+ }
+
+ @NonNull
+ private static String buildDocId(@NonNull String path) {
+ return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path);
+ }
+
+ @NonNull
+ private static String buildDocId(@NonNull String root, @NonNull String path) {
+ // docId format: root:path/to/file
+ return root + ':' + path;
+ }
}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 1cc2867ecf0e..0b7a568dd2f2 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -26,7 +26,7 @@ plugins {
}
allprojects {
- extra["jetpackComposeVersion"] = "1.6.0-alpha02"
+ extra["jetpackComposeVersion"] = "1.6.0-alpha07"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index df1d7d18555a..b001caddd000 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -18,8 +18,8 @@ package com.android.settingslib.spa.gallery.button
import android.os.Bundle
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
@@ -47,7 +47,7 @@ object ActionButtonPageProvider : SettingsPageProvider {
override fun Page(arguments: Bundle?) {
RegularScaffold(title = TITLE) {
val actionButtons = listOf(
- ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
)
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 8b563362a77b..aafae5f2129b 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
#
[versions]
-agp = "8.1.1"
+agp = "8.1.2"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
kotlin = "1.9.0"
-truth = "1.1"
+truth = "1.1.5"
[libraries]
dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index da04f426c68f..ce89de6ffd65 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index b8105113af7b..b73bbd8b80e1 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@ dependencies {
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-alpha04")
+ api("androidx.compose.material3:material3:1.2.0-alpha09")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.7.1")
+ api("androidx.navigation:navigation-compose:2.7.4")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 50243dcd8c9b..cce82354a51f 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -41,7 +41,7 @@ dependencies {
api("androidx.arch.core:core-testing:2.2.0-alpha01")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
- api("org.mockito.kotlin:mockito-kotlin:5.1.0")
+ api("org.mockito.kotlin:mockito-kotlin:2.2.11")
api("org.mockito:mockito-core") {
version {
strictly("2.28.2")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index f54de1514fcf..09cb98e22af3 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -22,6 +22,8 @@ import android.os.UserHandle
import android.os.UserManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -32,15 +34,15 @@ import kotlinx.coroutines.flow.flowOn
import com.android.settingslib.widget.restricted.R
data class Restrictions(
- val userId: Int,
+ val userId: Int = UserHandle.myUserId(),
val keys: List<String>,
)
sealed interface RestrictedMode
-object NoRestricted : RestrictedMode
+data object NoRestricted : RestrictedMode
-object BaseUserRestricted : RestrictedMode
+data object BaseUserRestricted : RestrictedMode
interface BlockedByAdmin : RestrictedMode {
fun getSummary(checked: Boolean?): String
@@ -79,6 +81,17 @@ interface RestrictionsProvider {
typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
+@Composable
+internal fun RestrictionsProviderFactory.rememberRestrictedMode(
+ restrictions: Restrictions,
+): State<RestrictedMode?> {
+ val context = LocalContext.current
+ val restrictionsProvider = remember(restrictions) {
+ this(context, restrictions)
+ }
+ return restrictionsProvider.restrictedModeState()
+}
+
internal class RestrictionsProviderImpl(
private val context: Context,
private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 1fa854a4c09e..17e970845f58 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,6 +45,7 @@ import com.android.settingslib.spaprivileged.model.app.userId
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.flow.Flow
@@ -149,14 +150,13 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>(
@Composable
fun getSummary(record: T): State<String> {
- val restrictionsProvider = remember(record.app.userId) {
- val restrictions = Restrictions(
+ val restrictions = remember(record.app.userId) {
+ Restrictions(
userId = record.app.userId,
keys = listModel.switchRestrictionKeys,
)
- restrictionsProviderFactory(context, restrictions)
}
- val restrictedMode = restrictionsProvider.restrictedModeState()
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)
val allowed = listModel.isAllowed(record)
return remember {
derivedStateOf {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
new file mode 100644
index 000000000000..50490c0b887d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+@Composable
+fun RestrictedPreference(
+ model: PreferenceModel,
+ restrictions: Restrictions,
+) {
+ RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedPreference(
+ model: PreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+ if (restrictions.keys.isEmpty()) {
+ Preference(model)
+ return
+ }
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ val restrictedSwitchModel = remember(restrictedMode) {
+ RestrictedPreferenceModel(model, restrictedMode)
+ }
+ restrictedSwitchModel.RestrictionWrapper {
+ Preference(restrictedSwitchModel)
+ }
+}
+
+private class RestrictedPreferenceModel(
+ model: PreferenceModel,
+ private val restrictedMode: RestrictedMode?,
+) : PreferenceModel {
+ override val title = model.title
+ override val summary = model.summary
+ override val icon = model.icon
+
+ override val enabled = when (restrictedMode) {
+ NoRestricted -> model.enabled
+ else -> stateOf(false)
+ }
+
+ override val onClick = when (restrictedMode) {
+ NoRestricted -> model.onClick
+ // Need to passthrough onClick for clickable semantics, although since enabled is false so
+ // this will not be called.
+ BaseUserRestricted -> model.onClick
+ else -> null
+ }
+
+ @Composable
+ fun RestrictionWrapper(content: @Composable () -> Unit) {
+ if (restrictedMode !is BlockedByAdmin) {
+ content()
+ return
+ }
+ Box(
+ Modifier
+ .clickable(
+ role = Role.Button,
+ onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+ )
+ ) { content() }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e77dcd4d9cc4..2129403c2d85 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spaprivileged.template.preference
import android.content.Context
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@@ -40,22 +41,29 @@ import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@Composable
fun RestrictedSwitchPreference(
model: SwitchPreferenceModel,
restrictions: Restrictions,
- restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+) {
+ RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedSwitchPreference(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
) {
if (restrictions.keys.isEmpty()) {
SwitchPreference(model)
return
}
val context = LocalContext.current
- val restrictionsProvider = remember(restrictions) {
- restrictionsProviderFactory(context, restrictions)
- }
- val restrictedMode = restrictionsProvider.restrictedModeState().value
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
val restrictedSwitchModel = remember(restrictedMode) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
@@ -112,8 +120,8 @@ private class RestrictedSwitchPreferenceModel(
override val onCheckedChange = when (restrictedMode) {
null -> null
is NoRestricted -> model.onCheckedChange
- // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
- // since changeable is false this will not be called.
+ // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+ // is false so this will not be called.
is BaseUserRestricted -> model.onCheckedChange
// Pass null since semantics ToggleableState is provided in RestrictionWrapper.
is BlockedByAdmin -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 86b6f027997d..f9abefc11e24 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,15 +16,15 @@
package com.android.settingslib.spaprivileged.template.scaffold
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@Composable
fun MoreOptionsScope.RestrictedMenuItem(
@@ -35,6 +35,7 @@ fun MoreOptionsScope.RestrictedMenuItem(
RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
}
+@VisibleForTesting
@Composable
internal fun MoreOptionsScope.RestrictedMenuItemImpl(
text: String,
@@ -42,12 +43,8 @@ internal fun MoreOptionsScope.RestrictedMenuItemImpl(
onClick: () -> Unit,
restrictionsProviderFactory: RestrictionsProviderFactory,
) {
- val context = LocalContext.current
- val restrictionsProvider = remember(restrictions) {
- restrictionsProviderFactory(context, restrictions)
- }
- val restrictedMode = restrictionsProvider.restrictedModeState().value
- MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
when (restrictedMode) {
is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
else -> onClick()
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
new file mode 100644
index 000000000000..eadf0ca0686d
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private var clicked = false
+
+ private val preferenceModel = object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = { clicked = true }
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_clickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
+ @Test
+ fun whenNoRestricted_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenNoRestricted_clickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_notClickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isFalse()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ private fun setContent(restrictions: Restrictions) {
+ composeTestRule.setContent {
+ RestrictedPreference(preferenceModel, restrictions) { _, _ ->
+ fakeRestrictionsProvider
+ }
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val USER_ID = 0
+ const val RESTRICTION_KEY = "restriction_key"
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1bb00b36f315..ce0772ff84a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.UserIcons;
import com.android.launcher3.icons.BaseIconFactory.IconOptions;
import com.android.launcher3.icons.IconFactory;
+import com.android.launcher3.util.UserIconInfo;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.settingslib.utils.BuildCompatUtils;
@@ -597,15 +598,25 @@ public class Utils {
/** Get the corresponding adaptive icon drawable. */
public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
- UserManager um = context.getSystemService(UserManager.class);
- boolean isClone = um.getProfiles(user.getIdentifier()).stream()
- .anyMatch(profile ->
- profile.isCloneProfile() && profile.id == user.getIdentifier());
+ int userType = UserIconInfo.TYPE_MAIN;
+ try {
+ UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
+ user.getIdentifier());
+ if (ui != null) {
+ if (ui.isCloneProfile()) {
+ userType = UserIconInfo.TYPE_CLONED;
+ } else if (ui.isManagedProfile()) {
+ userType = UserIconInfo.TYPE_WORK;
+ }
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
try (IconFactory iconFactory = IconFactory.obtain(context)) {
return iconFactory
.createBadgedIconBitmap(
icon,
- new IconOptions().setUser(user).setIsCloneProfile(isClone))
+ new IconOptions().setUser(new UserIconInfo(user, userType)))
.newIcon(context);
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f65f5a3c9297..9d3200dc340d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -62,6 +62,7 @@ systemui_compose_java_defaults {
static_libs: [
"CommunalLayoutLib",
"PlatformComposeCore",
+ "PlatformComposeSceneTransitionLayout",
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
@@ -164,7 +165,6 @@ android_library {
"SystemUISharedLib",
"SystemUI-statsd",
"SettingsLib",
- "com_android_systemui_communal_flags_lib",
"com_android_systemui_flags_lib",
"androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
@@ -243,9 +243,6 @@ filegroup {
"tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
- /* Log fakes */
- "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt",
-
/* QS fakes */
"tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
],
@@ -437,6 +434,7 @@ android_library {
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "flag-junit-base",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9bfc4be0f30d..58816310d495 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -83,6 +83,7 @@
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
<uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
<uses-permission android:name="android.permission.LOCATION_HARDWARE" />
+ <uses-permission android:name="android.permission.NETWORK_FACTORY" />
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -1062,5 +1063,9 @@
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
tools:node="remove" />
</provider>
+
+ <!-- Allow SystemUI to listen for the capabilities defined in the linked xml -->
+ <property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES"
+ android:value="@xml/self_certified_network_capabilities_both" />
</application>
</manifest>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0623d4a4b7fb..03f7c9968a1d 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -144,9 +144,32 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
"exclude-annotation": "android.platform.test.annotations.Postsubmit"
}
]
}
+ ],
+ // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged
+ "sysui-screenshot-test-staged": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index b18c7900a168..dc4208e87207 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -2,8 +2,7 @@ aconfig_declarations {
name: "com_android_systemui_flags",
package: "com.android.systemui",
srcs: [
- "systemui.aconfig",
- "accessibility.aconfig",
+ "*.aconfig",
],
}
@@ -11,16 +10,3 @@ java_aconfig_library {
name: "com_android_systemui_flags_lib",
aconfig_declarations: "com_android_systemui_flags",
}
-
-aconfig_declarations {
- name: "com_android_systemui_communal_flags",
- package: "com.android.systemui.communal",
- srcs: [
- "communal.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "com_android_systemui_communal_flags_lib",
- aconfig_declarations: "com_android_systemui_communal_flags",
-}
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
index 8ecb9842ddc5..2c6ff979cc7f 100644
--- a/packages/SystemUI/aconfig/communal.aconfig
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.systemui.communal"
+package: "com.android.systemui"
flag {
name: "communal_hub"
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
index a60b1de5b501..9a05504cad8b 100644
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ b/packages/SystemUI/communal/layout/tests/Android.bp
@@ -32,7 +32,7 @@ android_test {
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
"testables",
- "truth-prebuilt",
+ "truth",
],
libs: [
"android.test.mock",
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 3d670b809d15..c6e429a79e86 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,6 +22,7 @@ import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -66,6 +67,7 @@ object ComposeFacade : BaseComposeFacade {
override fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View {
throwComposeUnavailableError()
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 7b11ac7f4e1e..1722685f4287 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -31,6 +31,7 @@ import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.communal.ui.compose.CommunalHub
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -96,8 +97,11 @@ object ComposeFacade : BaseComposeFacade {
override fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View {
- return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } }
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
+ }
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index e4426fe97859..16c24375d14f 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,8 +31,10 @@ android_library {
],
static_libs: [
+ "CommunalLayoutLib",
"SystemUI-core",
"PlatformComposeCore",
+ "PlatformComposeSceneTransitionLayout",
"androidx.compose.runtime_runtime",
"androidx.compose.animation_animation-graphics",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 4d2978df7b1b..3d827fb5c9a6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -3,20 +3,63 @@ package com.android.systemui.communal.ui.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
+import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.integerResource
+import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
@Composable
-fun CommunalHub(modifier: Modifier = Modifier) {
+fun CommunalHub(
+ modifier: Modifier = Modifier,
+ viewModel: CommunalViewModel,
+) {
+ val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
Box(
modifier = modifier.fillMaxSize().background(Color.White),
) {
- Text(
- modifier = Modifier.align(Alignment.Center),
- text = "Hello Communal!",
+ CommunalGridLayout(
+ modifier = Modifier.align(Alignment.CenterStart),
+ layoutConfig =
+ CommunalGridLayoutConfig(
+ gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
+ gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
+ gridHeight = dimensionResource(R.dimen.communal_grid_height),
+ gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
+ ),
+ communalCards = if (showTutorial) tutorialContent else emptyList(),
)
}
}
+
+private val tutorialContent =
+ listOf(
+ tutorialCard(CommunalGridLayoutCard.Size.FULL),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ )
+
+private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
+ return object : CommunalGridLayoutCard() {
+ override val supportedSizes = listOf(size)
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ Card(modifier = modifier, content = {})
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index d1c12ac85cc5..f3bef7bf47da 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -19,6 +19,7 @@ package com.android.systemui.communal.ui.compose
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -32,7 +33,11 @@ import kotlinx.coroutines.flow.asStateFlow
/** The communal scene shows glanceable hub when the device is locked and docked. */
@SysUISingleton
-class CommunalScene @Inject constructor() : ComposableScene {
+class CommunalScene
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) : ComposableScene {
override val key = SceneKey.Communal
override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
@@ -45,6 +50,6 @@ class CommunalScene @Inject constructor() : ComposableScene {
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- CommunalHub(modifier)
+ CommunalHub(modifier, viewModel)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 591fa76f423f..4bbb78b69392 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -371,7 +371,8 @@ private fun StatusIcons(
val iconContainer = StatusIconContainer(context, null)
val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
- Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary),
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse),
)
statusBarIconController.addIconGroup(iconManager)
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
new file mode 100644
index 000000000000..050d1d5651ad
--- /dev/null
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "PlatformComposeSceneTransitionLayout",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+ use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml
new file mode 100644
index 000000000000..81131bb689e4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.compose.animation.scene">
+
+
+</manifest>
diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS
new file mode 100644
index 000000000000..33a59c2bcab3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+omarmt@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING
new file mode 100644
index 000000000000..f644a23ba0a3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+ "presubmit": [
+ {
+ "name": "PlatformComposeSceneTransitionLayoutTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "PlatformComposeCoreTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeFeaturesTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "SystemUIComposeGalleryTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 566967f920d3..041fc48dd09e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -17,12 +17,10 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.DisposableEffectResult
-import androidx.compose.runtime.DisposableEffectScope
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.unit.Dp
@@ -45,6 +43,20 @@ fun SceneScope.animateSharedIntAsState(
}
/**
+ * Animate a shared Int value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedIntAsState(
+ value: Int,
+ debugName: String,
+ canOverflow: Boolean = true,
+): State<Int> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
* Animate a shared Float value.
*
* @see SceneScope.animateSharedValueAsState
@@ -60,6 +72,20 @@ fun SceneScope.animateSharedFloatAsState(
}
/**
+ * Animate a shared Float value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedFloatAsState(
+ value: Float,
+ debugName: String,
+ canOverflow: Boolean = true,
+): State<Float> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
* Animate a shared Dp value.
*
* @see SceneScope.animateSharedValueAsState
@@ -75,6 +101,20 @@ fun SceneScope.animateSharedDpAsState(
}
/**
+ * Animate a shared Dp value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedDpAsState(
+ value: Dp,
+ debugName: String,
+ canOverflow: Boolean = true,
+): State<Dp> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+}
+
+/**
* Animate a shared Color value.
*
* @see SceneScope.animateSharedValueAsState
@@ -88,6 +128,19 @@ fun SceneScope.animateSharedColorAsState(
return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
}
+/**
+ * Animate a shared Color value.
+ *
+ * @see MovableElementScope.animateSharedValueAsState
+ */
+@Composable
+fun MovableElementScope.animateSharedColorAsState(
+ value: Color,
+ debugName: String,
+): State<Color> {
+ return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false)
+}
+
@Composable
internal fun <T> animateSharedValueAsState(
layoutImpl: SceneTransitionLayoutImpl,
@@ -98,33 +151,22 @@ internal fun <T> animateSharedValueAsState(
lerp: (T, T, Float) -> T,
canOverflow: Boolean,
): State<T> {
- val sharedValue = remember(key) { Element.SharedValue(key, value) }
+ val sharedValue =
+ Snapshot.withoutReadObservation {
+ element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) {
+ Element.SharedValue(key, value)
+ } as Element.SharedValue<T>
+ }
+
if (value != sharedValue.value) {
sharedValue.value = value
}
- DisposableEffect(element, scene, sharedValue) {
- addSharedValueToElement(element, scene, sharedValue)
- }
-
return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
}
}
-private fun <T> DisposableEffectScope.addSharedValueToElement(
- element: Element,
- scene: Scene,
- sharedValue: Element.SharedValue<T>,
-): DisposableEffectResult {
- val sceneValues =
- element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
- val sharedValues = sceneValues.sharedValues
-
- sharedValues[sharedValue.key] = sharedValue
- return onDispose { sharedValues.remove(sharedValue.key) }
-}
-
private fun <T> computeValue(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 88944f10eab9..88944f10eab9 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index ce96bbfc7976..ce96bbfc7976 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
index 98dbb67d7c66..98dbb67d7c66 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index bc015eedb1b4..bc015eedb1b4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 11bbf2aa987e..6dbeb69ff450 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -21,6 +21,7 @@ import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.ui.Modifier
@@ -36,8 +37,6 @@ import androidx.compose.ui.unit.IntSize
private const val TAG = "MovableElement"
-private object MovableElementScopeImpl : MovableElementScope
-
@Composable
internal fun MovableElement(
layoutImpl: SceneTransitionLayoutImpl,
@@ -51,6 +50,10 @@ internal fun MovableElement(
// every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we
// disable read observation during the look-up in that map.
val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) }
+ val movableElementScope =
+ remember(layoutImpl, element, scene) {
+ MovableElementScopeImpl(layoutImpl, element, scene)
+ }
// The [Picture] to which we save the last drawing commands of this element. This is
// necessary because the content of this element might not be composed in this scene, in
@@ -77,7 +80,7 @@ internal fun MovableElement(
}
}
) {
- element.movableContent { MovableElementScopeImpl.content() }
+ element.movableContent { movableElementScope.content() }
}
} else {
// If we are not composed, we draw the previous drawing commands at the same size as the
@@ -178,3 +181,20 @@ private fun shouldComposeMovableElement(
isHighestScene
}
}
+
+private class MovableElementScopeImpl(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val element: Element,
+ private val scene: Scene,
+) : MovableElementScope {
+ @Composable
+ override fun <T> animateSharedValueAsState(
+ value: T,
+ debugName: String,
+ lerp: (start: T, stop: T, fraction: Float) -> T,
+ canOverflow: Boolean,
+ ): State<T> {
+ val key = remember { ValueKey(debugName) }
+ return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index ccdec6ea8c5e..ccdec6ea8c5e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 3fd6828fca6b..3fd6828fca6b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 4283c0e61df8..74e66d2a9949 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -160,7 +160,16 @@ interface SceneScope {
// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
// arguments to allow sharing values inside a movable element.
-@ElementDsl interface MovableElementScope
+@ElementDsl
+interface MovableElementScope {
+ @Composable
+ fun <T> animateSharedValueAsState(
+ value: T,
+ debugName: String,
+ lerp: (start: T, stop: T, fraction: Float) -> T,
+ canOverflow: Boolean,
+ ): State<T>
+}
/** An action performed by the user. */
sealed interface UserAction
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 4952270cb5f2..4952270cb5f2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 7a21211c3dde..7a21211c3dde 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 75dcb2e44c13..75dcb2e44c13 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 1cbfe3057ff0..1cbfe3057ff0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 49669775fedd..49669775fedd 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index f1c27178391c..f1c27178391c 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 95385d51cb25..95385d51cb25 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index a1d63193bc73..a1d63193bc73 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 840800d838db..840800d838db 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index 17032dc288e0..17032dc288e0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
index 62d67f03f1d0..62d67f03f1d0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index 233ae597090b..233ae597090b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2ef8d56c6bc6..2ef8d56c6bc6 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 864b937a3fe0..864b937a3fe0 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
index 27f0948d5377..27f0948d5377 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
index 135a6e4ec4e4..135a6e4ec4e4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index cea8d9a65b43..cea8d9a65b43 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
index 793a9a59405a..793a9a59405a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
index 741f00d9f19b..741f00d9f19b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index eb1a634ff491..eb1a634ff491 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
new file mode 100644
index 000000000000..b53fae24865c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "PlatformComposeSceneTransitionLayoutTests",
+ manifest: "AndroidManifest.xml",
+ test_suites: ["device-tests"],
+ sdk_version: "current",
+ certificate: "platform",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "PlatformComposeSceneTransitionLayout",
+
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+
+ "truth",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+ use_resource_processor: true,
+}
diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..1a9172ee20e0
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.compose.animation.scene.tests" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.compose.animation.scene.tests"
+ android:label="Tests for SceneTransitionLayout"/>
+
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
new file mode 100644
index 000000000000..7b7695eebd2f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.ui.util.lerp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnimatedSharedAsStateTest {
+ @get:Rule val rule = createComposeRule()
+
+ private data class Values(
+ val int: Int,
+ val float: Float,
+ val dp: Dp,
+ val color: Color,
+ )
+
+ private fun lerp(start: Values, stop: Values, fraction: Float): Values {
+ return Values(
+ int = lerp(start.int, stop.int, fraction),
+ float = lerp(start.float, stop.float, fraction),
+ dp = lerp(start.dp, stop.dp, fraction),
+ color = lerp(start.color, stop.color, fraction),
+ )
+ }
+
+ @Composable
+ private fun SceneScope.Foo(
+ targetValues: Values,
+ onCurrentValueChanged: (Values) -> Unit,
+ ) {
+ val key = TestElements.Foo
+ Box(Modifier.element(key)) {
+ val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key)
+ val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key)
+ val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key)
+ val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key)
+
+ // Make sure we read the values during composition, so that we recompose and call
+ // onCurrentValueChanged() with the latest values.
+ val currentValues = Values(int, float, dp, color)
+ SideEffect { onCurrentValueChanged(currentValues) }
+ }
+ }
+
+ @Composable
+ private fun SceneScope.MovableFoo(
+ targetValues: Values,
+ onCurrentValueChanged: (Values) -> Unit,
+ ) {
+ val key = TestElements.Foo
+ MovableElement(key = key, Modifier) {
+ val int by
+ animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName)
+ val float by
+ animateSharedFloatAsState(
+ targetValues.float,
+ debugName = TestValues.Value2.debugName
+ )
+ val dp by
+ animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName)
+ val color by
+ animateSharedColorAsState(
+ targetValues.color,
+ debugName = TestValues.Value4.debugName
+ )
+
+ // Make sure we read the values during composition, so that we recompose and call
+ // onCurrentValueChanged() with the latest values.
+ val currentValues = Values(int, float, dp, color)
+ SideEffect { onCurrentValueChanged(currentValues) }
+ }
+ }
+
+ @Test
+ fun animateSharedValues() {
+ val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+ val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+ var lastValueInFrom = fromValues
+ var lastValueInTo = toValues
+
+ rule.testTransition(
+ fromSceneContent = {
+ Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it })
+ },
+ toSceneContent = {
+ Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+ },
+ transition = {
+ // The transition lasts 64ms = 4 frames.
+ spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+ },
+ fromScene = TestScenes.SceneA,
+ toScene = TestScenes.SceneB,
+ ) {
+ before {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+ // to was not composed yet, so lastValueInTo was not set yet.
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+
+ at(16) {
+ // Given that we use Modifier.element() here, animateSharedXAsState is composed in
+ // both scenes and values should be interpolated with the transition fraction.
+ val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+ assertThat(lastValueInFrom).isEqualTo(expectedValues)
+ assertThat(lastValueInTo).isEqualTo(expectedValues)
+ }
+
+ at(32) {
+ val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+ assertThat(lastValueInFrom).isEqualTo(expectedValues)
+ assertThat(lastValueInTo).isEqualTo(expectedValues)
+ }
+
+ at(48) {
+ val expectedValues = lerp(fromValues, toValues, fraction = 0.75f)
+ assertThat(lastValueInFrom).isEqualTo(expectedValues)
+ assertThat(lastValueInTo).isEqualTo(expectedValues)
+ }
+
+ after {
+ assertThat(lastValueInFrom).isEqualTo(toValues)
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+ }
+ }
+
+ @Test
+ fun movableAnimateSharedValues() {
+ val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+ val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+ var lastValueInFrom = fromValues
+ var lastValueInTo = toValues
+
+ rule.testTransition(
+ fromSceneContent = {
+ MovableFoo(
+ targetValues = fromValues,
+ onCurrentValueChanged = { lastValueInFrom = it }
+ )
+ },
+ toSceneContent = {
+ MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+ },
+ transition = {
+ // The transition lasts 64ms = 4 frames.
+ spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+ },
+ fromScene = TestScenes.SceneA,
+ toScene = TestScenes.SceneB,
+ ) {
+ before {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+ // to was not composed yet, so lastValueInTo was not set yet.
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+
+ at(16) {
+ // Given that we use MovableElement here, animateSharedXAsState is composed only
+ // once, in the highest scene (in this case, in toScene).
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
+ }
+
+ at(32) {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
+ }
+
+ at(48) {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
+ }
+
+ after {
+ assertThat(lastValueInFrom).isEqualTo(fromValues)
+ assertThat(lastValueInTo).isEqualTo(toValues)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 4204cd5f0da0..4204cd5f0da0 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 04b3f8a1dfe7..04b3f8a1dfe7 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 5afd420a5e16..5afd420a5e16 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index df3b72aa5533..df3b72aa5533 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
index e0ae1be69aaf..e0ae1be69aaf 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
index 83572620c88a..b4c393e9bfbe 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -37,6 +37,9 @@ object TestElements {
/** Value keys that can be reused by tests. */
object TestValues {
val Value1 = ValueKey("Value1")
+ val Value2 = ValueKey("Value2")
+ val Value3 = ValueKey("Value3")
+ val Value4 = ValueKey("Value4")
}
// We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index fa94b25028a2..fa94b25028a2 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
index 8ef6757d33bd..8ef6757d33bd 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
index d1205e727cf9..d1205e727cf9 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
index 2a27763f1d5c..2a27763f1d5c 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
index 384355ca951f..384355ca951f 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 2af363860272..e94eff32c30c 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -32,7 +32,6 @@ import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
import com.android.compose.animation.scene.inScene
import com.android.compose.animation.scene.testTransition
-import com.android.compose.modifiers.size
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.onEach
import org.junit.Rule
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
index 1d559fd6bd8a..1d559fd6bd8a 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index 03d231a7fcc6..03d231a7fcc6 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
index 8e2b77a2f2a0..8e2b77a2f2a0 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
index d6f64bfe4974..d6f64bfe4974 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
index fbd1b512c50a..fbd1b512c50a 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
index bf7bf98878e6..bf7bf98878e6 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a5e5aaa499f1..a9d2ee3cdfe6 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -71,7 +71,11 @@ public interface DarkIconDispatcher {
*/
void applyDark(DarkReceiver object);
+ /** The default tint (applicable for dark backgrounds) is white */
int DEFAULT_ICON_TINT = Color.WHITE;
+ /** To support an icon which wants to create contrast, the default tint is black-on-white. */
+ int DEFAULT_INVERSE_ICON_TINT = Color.BLACK;
+
Rect sTmpRect = new Rect();
int[] sTmpInt2 = new int[2];
@@ -88,6 +92,18 @@ public interface DarkIconDispatcher {
}
/**
+ * @return the tint to apply to a foreground, given that the background is tinted
+ * per {@link #getTint}
+ */
+ static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) {
+ if (isInAreas(tintAreas, view)) {
+ return inverseColor;
+ } else {
+ return DEFAULT_INVERSE_ICON_TINT;
+ }
+ }
+
+ /**
* @return true if more than half of the view area are in any of the given
* areas, false otherwise
*/
@@ -129,7 +145,40 @@ public interface DarkIconDispatcher {
*/
@ProvidesInterface(version = DarkReceiver.VERSION)
interface DarkReceiver {
- int VERSION = 2;
+ int VERSION = 3;
+
+ /**
+ * @param areas list of regions on screen where the tint applies
+ * @param darkIntensity float representing the level of tint. In the range [0,1]
+ * @param tint the tint applicable as a foreground contrast to the dark regions. This value
+ * is interpolated between a default light and dark tone, and is therefore
+ * usable as-is, as long as the view is in one of the areas defined in
+ * {@code areas}.
+ *
+ * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas}
+ *
+ * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+ * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+ * will be called in the same circumstances.
+ */
void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint);
+
+ /**
+ * New version of onDarkChanged, which describes a tint plus an optional contrastTint
+ * that can be used if the tint is applied to the background of an icon.
+ *
+ * We use the 2 here to avoid the case where an existing override of onDarkChanged
+ * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get
+ * mistakenly cast to (int) and therefore trigger this method.
+ *
+ * @param areas list of areas where dark tint applies
+ * @param tint int describing the tint color to use
+ * @param contrastTint if desired, a contrasting color that can be used for a foreground
+ *
+ * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or
+ * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both
+ * will be called in the same circumstances.
+ */
+ default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {}
}
}
diff --git a/packages/SystemUI/res-keyguard/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
new file mode 100644
index 000000000000..910a74ad5faf
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:paddingMode="stack">
+ <item
+ android:id="@android:id/background"
+ android:gravity="center_vertical|fill_horizontal">
+ <shape
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+ </shape>
+ </item>
+ <item
+ android:id="@android:id/progress"
+ android:gravity="center_vertical|fill_horizontal">
+ <clip>
+ <shape
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="?androidprv:attr/textColorPrimary" />
+ </shape>
+ </clip>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
new file mode 100644
index 000000000000..183f0e591c91
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<LinearLayout android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:layoutDirection="ltr"
+ android:gravity="center"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ProgressBar
+ android:id="@+id/side_fps_progress_bar"
+ android:layout_width="55dp"
+ android:layout_height="10dp"
+ android:indeterminateOnly="false"
+ android:min="0"
+ android:max="100"
+ android:progressDrawable="@drawable/progress_bar" />
+</LinearLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index a1e2dc36278b..a8017f65c810 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -52,15 +52,22 @@
android:visibility="gone"
/>
</FrameLayout>
- <ImageView
- android:id="@+id/mobile_type"
- android:layout_height="@dimen/status_bar_mobile_type_size"
+ <FrameLayout
+ android:id="@+id/mobile_type_container"
+ android:layout_height="@dimen/status_bar_mobile_container_height"
android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:adjustViewBounds="true"
- android:paddingStart="2.5sp"
- android:paddingEnd="1sp"
- android:visibility="gone" />
+ android:layout_marginStart="2.5sp"
+ android:layout_marginEnd="1sp"
+ android:visibility="gone"
+ >
+ <ImageView
+ android:id="@+id/mobile_type"
+ android:layout_height="@dimen/status_bar_mobile_type_size"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:adjustViewBounds="true"
+ />
+ </FrameLayout>
<Space
android:id="@+id/mobile_roaming_space"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background.xml b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
new file mode 100644
index 000000000000..db25c89d1fa5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/mobile_network_type_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ >
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius"
+ android:bottomRightRadius="0dp"
+ android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius"/>
+ <solid android:color="#FFF" />
+ <padding
+ android:left="2sp"
+ android:right="2sp"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index a51c55ee965f..8cfcb689eced 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -15,8 +15,9 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/cd_bottom_sheet"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="@dimen/dialog_side_padding"
@@ -26,11 +27,14 @@
<ImageView
android:id="@+id/connected_display_dialog_icon"
- android:layout_width="@dimen/screenrecord_logo_size"
- android:layout_height="@dimen/screenrecord_logo_size"
+ android:layout_width="@dimen/connected_display_dialog_logo_size"
+ android:layout_height="@dimen/connected_display_dialog_logo_size"
+ android:background="@drawable/circular_background"
+ android:backgroundTint="?androidprv:attr/materialColorPrimary"
android:importantForAccessibility="no"
+ android:padding="6dp"
android:src="@drawable/stat_sys_connected_display"
- android:tint="?androidprv:attr/materialColorPrimary" />
+ android:tint="?androidprv:attr/materialColorOnPrimary" />
<TextView
android:id="@+id/connected_display_dialog_title"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ea3c012afc43..1f671ac4c875 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -37,6 +37,9 @@
<bool name="config_use_large_screen_shade_header">true</bool>
+ <!-- Whether to show bottom sheets edge to edge -->
+ <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
+
<!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1add90ff4083..75e71e414262 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -947,6 +947,9 @@
<!-- Flag controlling whether visual query attention detection has been enabled. -->
<bool name="config_enableVisualQueryAttentionDetection">false</bool>
+ <!-- Whether to show bottom sheets edge to edge -->
+ <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
+
<!--
Whether the scene container framework is enabled.
@@ -954,4 +957,15 @@
bouncer, lockscreen, shade, and quick settings.
-->
<bool name="config_sceneContainerFrameworkEnabled">true</bool>
+
+ <!--
+ Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
+ TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
+ -->
+ <integer name="config_restToUnlockDuration">300</integer>
+
+ <!--
+ Width in pixels of the Side FPS sensor.
+ -->
+ <integer name="config_sfpsSensorWidth">200</integer>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5a83c7d2dc2a..10fd8b579b16 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -177,6 +177,12 @@
<!-- Size of the view displaying the mobile signal icon in the status bar. This value should
match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
<dimen name="status_bar_mobile_type_size">16sp</dimen>
+ <!-- Size of the view that contains the network type. Should be equal to
+ status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding -->
+ <dimen name="status_bar_mobile_container_height">18sp</dimen>
+ <!-- Corner radius for the background of the network type indicator. Should be equal to
+ status_bar_mobile_container_height / 2 -->
+ <dimen name="status_bar_mobile_container_corner_radius">9sp</dimen>
<!-- Size of the view displaying the mobile roam icon in the status bar. This value should
match the viewport size of drawable stat_sys_roaming -->
<dimen name="status_bar_mobile_roam_size">8sp</dimen>
@@ -760,6 +766,8 @@
<dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+ <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
+ <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -1355,6 +1363,9 @@
<dimen name="screenrecord_options_padding_bottom">16dp</dimen>
<dimen name="screenrecord_buttons_margin_top">20dp</dimen>
+ <!-- Connected display dialog -->
+ <dimen name="connected_display_dialog_logo_size">48dp</dimen>
+
<!-- Keyguard user switcher -->
<dimen name="kg_user_switcher_text_size">16sp</dimen>
@@ -1658,6 +1669,15 @@
<!-- Height percentage of the parent container occupied by the communal view -->
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+ <!-- Size of each communal grid column -->
+ <dimen name="communal_grid_column_size">64dp</dimen>
+ <!-- Size of each communal grid gutter between columns -->
+ <dimen name="communal_grid_gutter_size">16dp</dimen>
+ <!-- Height of the communal grid layout -->
+ <dimen name="communal_grid_height">630dp</dimen>
+ <!-- Number of columns for each communal card -->
+ <integer name="communal_grid_columns_per_card">6</integer>
+
<dimen name="drag_and_drop_icon_size">70dp</dimen>
<dimen name="qs_tile_service_request_dialog_width">304dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 321594f41479..7a6d29ab3c1f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -209,6 +209,8 @@
<string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
<string name="screenshot_failed_title">Couldn\'t save screenshot</string>
+ <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] -->
+ <string name="screenshot_failed_external_display_indication">External Display</string>
<!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
<string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
<!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
@@ -711,8 +713,8 @@
<!-- QuickSettings: Cast detail panel, default device description [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_cast_detail_empty_text">No devices available</string>
- <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] -->
- <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string>
+ <!-- QuickSettings: Cast unavailable, text when not connected to WiFi or ethernet[CHAR LIMIT=NONE] -->
+ <string name="quick_settings_cast_no_network">No Wi\u2011Fi or Ethernet connection</string>
<!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
<string name="quick_settings_brightness_dialog_title">Brightness</string>
<!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 000000000000..4b430e146439
--- /dev/null
+++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 3360c967a182..aef83710c17b 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@ object FlagsFactory {
val knownFlags: Map<String, Flag<*>>
get() {
// We need to access Flags in order to initialize our map.
- assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
return flagMap
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 75465c27724d..f4b429659d8a 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -24,7 +24,7 @@ object FlagsFactory {
val knownFlags: Map<String, Flag<*>>
get() {
// We need to access Flags in order to initialize our map.
- assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }
return flagMap
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 01a75d9e0f16..e47d36f76a14 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,8 @@
*/
package com.android.keyguard
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -37,7 +39,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -61,6 +63,7 @@ import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import java.util.Locale
import java.util.TimeZone
@@ -297,7 +300,7 @@ constructor(
object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
- if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
if (!isKeyguardVisible) {
clock?.run {
smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -342,9 +345,9 @@ constructor(
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
disposableHandle =
parent.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
- if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
} else {
@@ -454,8 +457,9 @@ constructor(
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToAodTransition
- .filter { it.transitionState == TransitionState.FINISHED }
+ keyguardTransitionInteractor.transitionStepsToState(AOD)
+ .filter { it.transitionState == TransitionState.STARTED }
+ .filter { it.from != LOCKSCREEN }
.collect { handleDoze(1f) }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 79642bdae1ce..758d1fef6e2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -54,6 +54,9 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -102,10 +105,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
private final Rect mClipBounds = new Rect();
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
-
+ private boolean mGoneToAodTransitionRunning = false;
private DumpManager mDumpManager;
private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -135,6 +139,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
PowerInteractor powerInteractor) {
super(keyguardStatusView);
@@ -144,12 +149,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
mConfigurationController = configurationController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
- logger.getBuffer());
+ featureFlags, logger.getBuffer());
mInteractionJankMonitor = interactionJankMonitor;
mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -199,6 +205,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
dozeTimeTick();
}
}, context);
+
+ collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+ (TransitionStep step) -> {
+ if (step.getTransitionState() == TransitionState.RUNNING) {
+ mGoneToAodTransitionRunning = true;
+ } else {
+ mGoneToAodTransitionRunning = false;
+ }
+ }, context);
}
public KeyguardStatusView getView() {
@@ -266,7 +281,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
mView.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b7bb35eb6783..205c297cebc4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -153,7 +153,6 @@ import com.android.settingslib.Utils;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -177,6 +176,7 @@ import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -1984,6 +1984,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationAcquired(int acquireInfo) {
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
+ mLogger.logFingerprintAcquired(acquireInfo);
handleFingerprintAcquired(acquireInfo);
Trace.endSection();
}
@@ -3462,7 +3463,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Deprecated
private boolean isUnlockWithFacePossible(int userId) {
if (isFaceAuthInteractorEnabled()) {
- return getFaceAuthInteractor().canFaceAuthRun();
+ return getFaceAuthInteractor() != null
+ && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();
}
return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index c64ae0106b4a..d524e4a8c408 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,6 +23,8 @@ import android.util.Property;
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.StatusBarState;
@@ -53,6 +55,7 @@ public class KeyguardVisibilityHelper {
private boolean mKeyguardViewVisibilityAnimating;
private boolean mLastOccludedState = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
+ private final FeatureFlags mFeatureFlags;
private final LogBuffer mLogBuffer;
public KeyguardVisibilityHelper(View view,
@@ -60,12 +63,14 @@ public class KeyguardVisibilityHelper {
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
boolean animateYPos,
+ FeatureFlags featureFlags,
LogBuffer logBuffer) {
mView = view;
mKeyguardStateController = keyguardStateController;
mDozeParameters = dozeParameters;
mScreenOffAnimationController = screenOffAnimationController;
mAnimateYPos = animateYPos;
+ mFeatureFlags = featureFlags;
mLogBuffer = logBuffer;
}
@@ -162,13 +167,17 @@ public class KeyguardVisibilityHelper {
animProps,
true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
- log("ScreenOff transition");
- mKeyguardViewVisibilityAnimating = true;
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ log("Using GoneToAodTransition");
+ mKeyguardViewVisibilityAnimating = false;
+ } else {
+ log("ScreenOff transition");
+ mKeyguardViewVisibilityAnimating = true;
- // Ask the screen off animation controller to animate the keyguard visibility for us
- // since it may need to be cancelled due to keyguard lifecycle events.
- mScreenOffAnimationController.animateInKeyguard(
- mView, mSetVisibleEndRunnable);
+ // Ask the screen off animation controller to animate the keyguard visibility
+ // for us since it may need to be cancelled due to keyguard lifecycle events.
+ mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable);
+ }
} else {
log("Direct set Visibility to VISIBLE");
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index fe19616cef1d..fa07072b7fe1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -44,6 +44,7 @@ import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
private const val TAG = "KeyguardUpdateMonitorLog"
+private const val FP_LOG_TAG = "KeyguardFingerprintLog"
/** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */
class KeyguardUpdateMonitorLogger
@@ -157,7 +158,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logFingerprintAuthForWrongUser(authUserId: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = authUserId },
{ "Fingerprint authenticated for wrong user: $int1" }
@@ -166,7 +167,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logFingerprintDisabledForUser(userId: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = userId },
{ "Fingerprint disabled by DPM for userId: $int1" }
@@ -174,12 +175,17 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
}
fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
- logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ { int1 = mode },
+ { "handleFingerprintLockoutReset: $int1" }
+ )
}
fun logFingerprintRunningState(fingerprintRunningState: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = fingerprintRunningState },
{ "fingerprintRunningState: $int1" }
@@ -188,7 +194,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
int1 = userId
@@ -212,7 +218,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
int1 = userId
@@ -224,7 +230,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
fun logFingerprintError(msgId: Int, originalErrMsg: String) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
str1 = originalErrMsg
@@ -751,4 +757,25 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
{ "userSwitchComplete: $str1, userId: $int1" }
)
}
+
+ fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) {
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ {
+ int1 = helpMsgId
+ str1 = "$helpString"
+ },
+ { "fingerprint help message: $int1, $str1" }
+ )
+ }
+
+ fun logFingerprintAcquired(acquireInfo: Int) {
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ { int1 = acquireInfo },
+ { "fingerprint acquire message: $int1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c1f6259d94f7..40f229b7004a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -41,6 +41,8 @@ import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.View.AccessibilityDelegate
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
@@ -54,13 +56,13 @@ import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.keyguard.KeyguardPINView
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
@@ -229,6 +231,20 @@ constructor(
}
}
+ /** Hide the arrow indicator. */
+ fun hideIndicator() {
+ val lottieAnimationView =
+ overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+ lottieAnimationView?.visibility = INVISIBLE
+ }
+
+ /** Show the arrow indicator. */
+ fun showIndicator() {
+ val lottieAnimationView =
+ overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+ lottieAnimationView?.visibility = VISIBLE
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("requests:")
for (requestSource in requests) {
@@ -247,6 +263,10 @@ constructor(
pw.println(" displayId=${displayInfo.uniqueId}")
pw.println(" sensorType=${sensorProps?.sensorType}")
pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+ pw.println("lottieAnimationView:")
+ pw.println(
+ " visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}"
+ )
pw.println("overlayOffsets=$overlayOffsets")
pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
@@ -498,5 +518,5 @@ enum class SideFpsUiRequestSource {
AUTO_SHOW,
/** Pin, pattern or password bouncer */
PRIMARY_BOUNCER,
- ALTERNATE_BOUNCER
+ ALTERNATE_BOUNCER,
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
index a590dccd5318..b9b2fd8875d9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
@@ -23,8 +23,6 @@ import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
@@ -49,10 +47,4 @@ interface BiometricsDomainLayerModule {
@Binds
@SysUISingleton
fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
-
- @Binds
- @SysUISingleton
- fun providesSideFpsOverlayInteractor(
- impl: SideFpsOverlayInteractorImpl
- ): SideFpsOverlayInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index f36a3ec89e3f..a317a0684055 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -54,6 +54,9 @@ interface DisplayStateInteractor {
/** Current rotation of the display */
val currentRotation: StateFlow<DisplayRotation>
+ /** Display change event indicating a change to the given displayId has occurred. */
+ val displayChanges: Flow<Int>
+
/** Called on configuration changes, used to keep the display state in sync */
fun onConfigurationChanged(newConfig: Configuration)
}
@@ -74,6 +77,8 @@ constructor(
screenSizeFoldProvider = foldProvider
}
+ override val displayChanges = displayRepository.displayChangeEvent
+
override val isFolded: Flow<Boolean> =
conflatedCallbackFlow {
val sendFoldStateUpdate = { state: Boolean ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
deleted file mode 100644
index 75ae061f8cce..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
-
-/** Business logic for SideFps overlay offsets. */
-interface SideFpsOverlayInteractor {
-
- /** The displayId of the current display. */
- val displayId: Flow<String>
-
- /** Overlay offsets corresponding to given displayId. */
- val overlayOffsets: Flow<SensorLocationInternal>
-
- /** Called on display changes, used to keep the display state in sync */
- fun onDisplayChanged(displayId: String)
-}
-
-@SysUISingleton
-class SideFpsOverlayInteractorImpl
-@Inject
-constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
- SideFpsOverlayInteractor {
-
- private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
- override val displayId: Flow<String> = _displayId.asStateFlow()
-
- override val overlayOffsets: Flow<SensorLocationInternal> =
- combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
- offsets[displayId] ?: SensorLocationInternal.DEFAULT
- }
-
- override fun onDisplayChanged(displayId: String) {
- _displayId.value = displayId
- }
-
- companion object {
- private const val TAG = "SideFpsOverlayInteractorImpl"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
new file mode 100644
index 000000000000..f85203ea2076
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import android.view.WindowManager
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class SideFpsSensorInteractor
+@Inject
+constructor(
+ private val context: Context,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+ windowManager: WindowManager,
+ displayStateInteractor: DisplayStateInteractor,
+ featureFlags: FeatureFlagsClassic,
+) {
+
+ private val sensorForCurrentDisplay =
+ combine(
+ displayStateInteractor.displayChanges,
+ fingerprintPropertyRepository.sensorLocations,
+ ::Pair
+ )
+ .map { (_, locations) -> locations[context.display?.uniqueId] }
+ .filterNotNull()
+
+ val isAvailable: Flow<Boolean> =
+ fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
+
+ val authenticationDuration: Flow<Long> =
+ flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L)
+
+ val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+ isAvailable.flatMapLatest { sfpsAvailable ->
+ if (sfpsAvailable) {
+ // todo (b/305236201) also add the settings check here.
+ flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK))
+ } else {
+ flowOf(false)
+ }
+ }
+
+ val sensorLocation: Flow<SideFpsSensorLocation> =
+ combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
+ (rotation, sensorLocation: SensorLocationInternal) ->
+ val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
+ // device dimensions in the current rotation
+ val size = windowManager.maximumWindowMetrics.bounds
+ val isDefaultOrientation = rotation.isDefaultOrientation()
+ // Width and height are flipped is device is not in rotation_0 or rotation_180
+ // Flipping it to the width and height of the device in default orientation.
+ val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+ val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+ val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0
+
+ val (sensorLeft, sensorTop) =
+ if (isSensorVerticalInDefaultOrientation) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> {
+ Pair(displayWidth, sensorLocation.sensorLocationY)
+ }
+ DisplayRotation.ROTATION_90 -> {
+ Pair(sensorLocation.sensorLocationY, 0)
+ }
+ DisplayRotation.ROTATION_180 -> {
+ Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
+ }
+ DisplayRotation.ROTATION_270 -> {
+ Pair(
+ displayHeight - sensorLocation.sensorLocationY - sensorWidth,
+ displayWidth
+ )
+ }
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> {
+ Pair(sensorLocation.sensorLocationX, 0)
+ }
+ DisplayRotation.ROTATION_90 -> {
+ Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
+ }
+ DisplayRotation.ROTATION_180 -> {
+ Pair(
+ displayWidth - sensorLocation.sensorLocationX - sensorWidth,
+ displayHeight
+ )
+ }
+ DisplayRotation.ROTATION_270 -> {
+ Pair(displayHeight, sensorLocation.sensorLocationX)
+ }
+ }
+ }
+
+ SideFpsSensorLocation(
+ left = sensorLeft,
+ top = sensorTop,
+ width = sensorWidth,
+ isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
new file mode 100644
index 000000000000..35f8e3bb461f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.model
+
+data class SideFpsSensorLocation(
+ /** Pixel offset from the left of the screen */
+ val left: Int,
+ /** Pixel offset from the top of the screen */
+ val top: Int,
+ /** Width in pixels of the SFPS sensor */
+ val width: Int,
+ /**
+ * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or
+ * Rotation_180)
+ */
+ val isSensorVerticalInDefaultOrientation: Boolean
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
index 10a3e915fe80..336404c04dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt
@@ -10,6 +10,9 @@ enum class DisplayRotation {
ROTATION_270,
}
+fun DisplayRotation.isDefaultOrientation() =
+ this == DisplayRotation.ROTATION_0 || this == DisplayRotation.ROTATION_180
+
/** Converts [Surface.Rotation] to corresponding [DisplayRotation] */
fun Int.toDisplayRotation(): DisplayRotation =
when (this) {
@@ -19,3 +22,12 @@ fun Int.toDisplayRotation(): DisplayRotation =
Surface.ROTATION_270 -> DisplayRotation.ROTATION_270
else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this")
}
+
+/** Converts [DisplayRotation] to corresponding [Surface.Rotation] */
+fun DisplayRotation.toRotation(): Int =
+ when (this) {
+ DisplayRotation.ROTATION_0 -> Surface.ROTATION_0
+ DisplayRotation.ROTATION_90 -> Surface.ROTATION_90
+ DisplayRotation.ROTATION_180 -> Surface.ROTATION_180
+ DisplayRotation.ROTATION_270 -> Surface.ROTATION_270
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
new file mode 100644
index 000000000000..b8e2de404628
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.dagger
+
+import com.android.systemui.communal.data.repository.CommunalRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ CommunalRepositoryModule::class,
+ CommunalTutorialRepositoryModule::class,
+ CommunalWidgetRepositoryModule::class,
+ ]
+)
+class CommunalModule
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
index 932dbfb093ce..ad02f6280a64 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
@@ -2,6 +2,7 @@ package com.android.systemui.communal.ui.view.layout.sections
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.removeView
@@ -9,14 +10,20 @@ import com.android.systemui.res.R
import javax.inject.Inject
/** A keyguard section that hosts the communal hub. */
-class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() {
+class DefaultCommunalHubSection
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) : KeyguardSection() {
private val communalHubViewId = R.id.communal_hub
override fun addViews(constraintLayout: ConstraintLayout) {
constraintLayout.addView(
- ComposeFacade.createCommunalView(constraintLayout.context).apply {
- id = communalHubViewId
- },
+ ComposeFacade.createCommunalView(
+ context = constraintLayout.context,
+ viewModel = viewModel,
+ )
+ .apply { id = communalHubViewId },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
new file mode 100644
index 000000000000..ddeb1d67b945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class CommunalViewModel
+@Inject
+constructor(
+ tutorialInteractor: CommunalTutorialInteractor,
+) {
+ /** Whether communal hub should show tutorial content. */
+ val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 5c1539a7fcf3..9221832b4ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,6 +22,7 @@ import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -73,8 +74,9 @@ interface BaseComposeFacade {
sceneByKey: Map<SceneKey, Scene>,
): View
- /** Create a [View] that represents the communal hub. */
+ /** Create a [View] to represent [viewModel] on screen. */
fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 04a9cae31382..d57f31f91df1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -67,6 +67,7 @@ import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.MediaRouter2Manager;
+import android.media.projection.IMediaProjectionManager;
import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.net.ConnectivityManager;
@@ -414,6 +415,13 @@ public class FrameworkServicesModule {
}
@Provides
+ @Singleton
+ static IMediaProjectionManager provideIMediaProjectionManager() {
+ return IMediaProjectionManager.Stub.asInterface(
+ ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
+ }
+
+ @Provides
static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
return MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7d4e1a1011db..f0d7592d8940 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,6 +42,7 @@ import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
@@ -170,6 +171,7 @@ import javax.inject.Named;
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommonRepositoryModule.class,
+ CommunalModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index 7510cf6ce04c..d19efbdd8026 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -15,14 +15,12 @@
*/
package com.android.systemui.display.ui.view
-import android.app.Dialog
import android.content.Context
import android.os.Bundle
-import android.view.Gravity
import android.view.View
-import android.view.WindowManager
import android.widget.TextView
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
/**
* Dialog used to decide what to do with a connected display.
@@ -35,7 +33,7 @@ class MirroringConfirmationDialog(
private val onStartMirroringClickListener: View.OnClickListener,
private val onCancelMirroring: View.OnClickListener,
theme: Int = R.style.Theme_SystemUI_Dialog,
-) : Dialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, theme) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
@@ -43,13 +41,8 @@ class MirroringConfirmationDialog(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- window?.apply {
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
- setGravity(Gravity.BOTTOM)
- }
setContentView(R.layout.connected_display_dialog)
- setCanceledOnTouchOutside(true)
+
mirrorButton =
requireViewById<TextView>(R.id.enable_display).apply {
setOnClickListener(onStartMirroringClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 694695017efd..1c2ff4b1b42c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -36,6 +36,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.FeatureFlags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.settings.GlobalSettings;
@@ -80,6 +81,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>();
private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
+ private final FeatureFlags mGantryFlags;
private final Restarter mRestarter;
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -124,6 +126,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
@Main Resources resources,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
+ FeatureFlags gantryFlags,
Restarter restarter) {
mFlagManager = flagManager;
mContext = context;
@@ -132,6 +135,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
mSystemProperties = systemProperties;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
+ mGantryFlags = gantryFlags;
mRestarter = restarter;
}
@@ -259,9 +263,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
if (!hasServerOverride
&& !defaultValue
&& result == null
- && !flag.getName().equals(Flags.TEAMFOOD.getName())
&& flag.getTeamfood()) {
- return isEnabled(Flags.TEAMFOOD);
+ return mGantryFlags.sysuiTeamfood();
}
return result == null ? mServerFlagReader.readServerOverride(
@@ -534,7 +537,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("can override: true");
-
+ pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
pw.println("booleans: " + mBooleanFlagCache.size());
// Sort our flags for dumping
TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 11ac39ff867b..081618ef9d63 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -36,7 +36,10 @@ import com.android.systemui.flags.FlagsFactory.unreleasedFlag
* See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb.
*/
object Flags {
- @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
+ // IGNORE ME!
+ // Because flags are static, we need an ever-present flag to reference in some of the internal
+ // code that ensure that other flags are referenced and available.
+ @JvmField val NULL_FLAG = unreleasedFlag("null_flag")
// 100 - notification
// TODO(b/297792660): Tracking Bug
@@ -401,6 +404,9 @@ object Flags {
@JvmField val SIGNAL_CALLBACK_DEPRECATION =
unreleasedFlag("signal_callback_deprecation", teamfood = true)
+ // TODO(b/301610137): Tracking bug
+ @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true)
+
// TODO(b/265892345): Tracking Bug
val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
@@ -497,10 +503,9 @@ object Flags {
@Keep
@JvmField
val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- unreleasedFlag(
- name = "record_task_content",
+ releasedFlag(
+ name = "enable_record_task_content",
namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- teamfood = true
)
// TODO(b/254512674): Tracking Bug
@@ -620,7 +625,7 @@ object Flags {
/** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
@JvmField
- val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+ val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 3fe68062f19a..7ccc26c063d3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -22,11 +22,11 @@ import com.android.systemui.Dependency
/**
* This class promotes best practices for flag guarding System UI view refactors.
* * [isEnabled] allows changing an implementation.
- * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
+ * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and
* ensure that it is not being invoked accidentally in the post-flag refactor.
- * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
- * flag-disabled builds, but with a check that should crash eng builds or tests when the
- * expectation is violated.
+ * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when
+ * invoked on flag-disabled builds, but with a check that should crash eng builds or tests when
+ * the expectation is violated.
*
* The constructors require that you provide a [FeatureFlags] instance. If you're using this in a
* View class, it's acceptable to ue the [forView] constructor methods, which do not require one,
@@ -60,13 +60,13 @@ private constructor(
* Example usage:
* ```
* public void setController(NotificationShelfController notificationShelfController) {
- * mShelfRefactor.assertDisabled();
+ * mShelfRefactor.assertInLegacyMode();
* mController = notificationShelfController;
* }
* ````
*/
- fun assertDisabled() =
- check(!isEnabled) { "Code path not supported when $flagName is enabled." }
+ fun assertInLegacyMode() =
+ check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -76,18 +76,17 @@ private constructor(
* Example usage:
* ```
* public void setShelfIcons(NotificationIconContainer icons) {
- * if (mShelfRefactor.expectEnabled()) {
- * mShelfIcons = icons;
- * }
+ * if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+ * mShelfIcons = icons;
* }
* ```
*/
- fun expectEnabled(): Boolean {
+ fun isUnexpectedlyInLegacyMode(): Boolean {
if (!isEnabled) {
- val message = "Code path not supported when $flagName is disabled."
+ val message = "New code path expects $flagName to be enabled."
Log.wtf(TAG, message, Exception(message))
}
- return isEnabled
+ return !isEnabled
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 86bf368791bc..a511713eddd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard
import android.content.Context
import android.view.LayoutInflater
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
import com.android.keyguard.LockIconView
@@ -71,6 +72,7 @@ constructor(
private val keyguardIndicationController: KeyguardIndicationController,
private val lockIconViewController: LockIconViewController,
private val shadeInteractor: ShadeInteractor,
+ private val interactionJankMonitor: InteractionJankMonitor
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -140,6 +142,7 @@ constructor(
keyguardStateController,
shadeInteractor,
{ keyguardStatusViewController!!.getClockController() },
+ interactionJankMonitor,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 081edd152538..019d4283a03c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -38,9 +38,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
-import com.android.systemui.communal.data.repository.CommunalRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -96,9 +93,6 @@ import kotlinx.coroutines.CoroutineDispatcher;
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
includes = {
- CommunalRepositoryModule.class,
- CommunalTutorialRepositoryModule.class,
- CommunalWidgetRepositoryModule.class,
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index e8740a4b24c5..654f2d106206 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -90,7 +90,7 @@ interface BiometricSettingsRepository {
* If the current user can use face auth to enter the device. This is true when the user has
* face auth enrolled, and is enabled in settings/device policy.
*/
- val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
/**
* If the current user can use face auth to enter the device right now. This is true when
@@ -348,10 +348,11 @@ constructor(
.and(isFingerprintBiometricAllowed)
.stateIn(scope, SharingStarted.Eagerly, false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> =
+ override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> =
isFaceAuthenticationEnabled
.and(isFaceEnrolled)
.and(mobileConnectionsRepository.isAnySimSecure.isFalse())
+ .stateIn(scope, SharingStarted.Eagerly, false)
override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =
isFaceAuthEnrolledAndEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 2f8010620e5e..565962394db1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -22,6 +22,7 @@ import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -32,6 +33,11 @@ interface KeyguardRepositoryModule {
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
+ @IntoMap
+ @ClassKey(SideFpsProgressBarViewBinder::class)
+ fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable
+
+ @Binds
fun keyguardSurfaceBehindRepository(
impl: KeyguardSurfaceBehindRepositoryImpl
): KeyguardSurfaceBehindRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 38eb730d1498..6e0aa4c7682d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -26,10 +26,11 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
-import javax.inject.Inject
@SysUISingleton
class FromAodTransitionInteractor
@@ -86,15 +87,19 @@ constructor(
}
}
}
-
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
- duration = TRANSITION_DURATION_MS
+ duration =
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ val TO_LOCKSCREEN_DURATION = 500.milliseconds
+ private val DEFAULT_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index ad51e74090d9..c67153a5963d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -114,7 +114,8 @@ constructor(
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+ if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+ resetIfCancelled = true,
)
}
}
@@ -127,6 +128,7 @@ constructor(
duration =
when (toState) {
KeyguardState.DREAMING -> TO_DREAMING_DURATION
+ KeyguardState.AOD -> TO_AOD_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -134,5 +136,6 @@ constructor(
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
+ val TO_AOD_DURATION = 1100.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 660bd84006d7..c39a4c9c4172 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -33,6 +33,9 @@ import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -40,9 +43,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -355,6 +355,7 @@ constructor(
when (toState) {
KeyguardState.DREAMING -> TO_DREAMING_DURATION
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+ KeyguardState.AOD -> TO_AOD_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -364,5 +365,6 @@ constructor(
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
+ val TO_AOD_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 89aca7631934..85b0f4fb864b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -41,6 +41,9 @@ interface KeyguardFaceAuthInteractor {
/** Whether face auth is in lock out state. */
fun isLockedOut(): Boolean
+ /** Whether face auth is enrolled and enabled for the current user */
+ fun isFaceAuthEnabledAndEnrolled(): Boolean
+
/**
* Register listener for use from code that cannot use [authenticationStatus] or
* [detectionStatus]
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6e19fdbefab5..b953b4879a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -39,7 +39,6 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -49,6 +48,8 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -64,8 +65,6 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
-import javax.inject.Provider
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -148,9 +147,7 @@ constructor(
.combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
isDreaming && isDozeOff(dozeTransitionModel.to)
}
- .sample(powerInteractor.isAwake) { isAbleToDream, isAwake ->
- isAbleToDream && isAwake
- }
+ .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
.flatMapLatest { isAbleToDream ->
flow {
delay(50)
@@ -217,11 +214,8 @@ constructor(
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
/** Notifies when a new configuration is set */
- val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
-
- /** Represents the current state of the KeyguardRootView visibility */
- val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
- repository.keyguardRootViewVisibility
+ val configurationChange: Flow<Unit> =
+ configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
/** The position of the keyguard clock. */
val clockPosition: Flow<Position> = repository.clockPosition
@@ -235,12 +229,17 @@ constructor(
R.dimen.keyguard_translate_distance_on_swipe_up
)
shadeRepository.shadeModel.map {
- // On swipe up, translate the keyguard to reveal the bouncer
- MathUtils.lerp(
- translationDistance,
- 0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
- )
+ if (it.expansionAmount == 0f) {
+ // Reset the translation value
+ 0f
+ } else {
+ // On swipe up, translate the keyguard to reveal the bouncer
+ MathUtils.lerp(
+ translationDistance,
+ 0,
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index f38bb2b519e7..fbadde63a6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -43,6 +43,7 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt
override fun isLockedOut(): Boolean = false
override fun isEnabled() = false
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
override fun registerListener(listener: FaceAuthenticationListener) {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 797dec2c9625..2641846251cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
@@ -81,6 +82,7 @@ constructor(
private val facePropertyRepository: FacePropertyRepository,
private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
private val powerInteractor: PowerInteractor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
) : CoreStartable, KeyguardFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -149,7 +151,10 @@ constructor(
.onEach {
if (it) {
faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
- repository.setLockedOut(true)
+ // We don't care about this if face auth is not enabled.
+ if (isFaceAuthEnabledAndEnrolled()) {
+ repository.setLockedOut(true)
+ }
}
}
.launchIn(applicationScope)
@@ -263,6 +268,9 @@ constructor(
}
}
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean =
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value
+
private fun observeFaceAuthStateUpdates() {
authenticationStatus
.onEach { authStatusUpdate ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index ae18681a5b92..3c143fe1a68a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.shared.model
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
@@ -39,7 +41,12 @@ data class HelpFingerprintAuthenticationStatus(
/** Fingerprint acquired message. */
data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
- FingerprintAuthenticationStatus()
+ FingerprintAuthenticationStatus() {
+
+ val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
+
+ val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD
+}
/** Fingerprint authentication failed message. */
object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ac4ad39a38a5..c72e6ce0b7d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -20,21 +20,24 @@ import android.annotation.DrawableRes
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
+import android.view.ViewGroup.OnHierarchyChangeListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -48,8 +51,6 @@ import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
object KeyguardRootViewBinder {
- private var onLayoutChangeListener: OnLayoutChange? = null
-
@JvmStatic
fun bind(
view: ViewGroup,
@@ -60,7 +61,14 @@ object KeyguardRootViewBinder {
keyguardStateController: KeyguardStateController,
shadeInteractor: ShadeInteractor,
clockControllerProvider: Provider<ClockController>?,
+ interactionJankMonitor: InteractionJankMonitor?,
): DisposableHandle {
+ var onLayoutChangeListener: OnLayoutChange? = null
+ val childViews = mutableMapOf<Int, View?>()
+ val statusViewId = R.id.keyguard_status_view
+ val burnInLayerId = R.id.burn_in_layer
+ val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+ val largeClockId = R.id.lockscreen_clock_view_large
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -86,36 +94,74 @@ object KeyguardRootViewBinder {
if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
+ viewModel.burnInLayerVisibility.collect { visibility ->
+ childViews[burnInLayerId]?.visibility = visibility
+ // Reset alpha only for the icons, as they currently have their
+ // own animator
+ childViews[aodNotificationIconContainerId]?.alpha = 0f
+ }
+ }
+
+ launch {
+ viewModel.burnInLayerAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
+ childViews[aodNotificationIconContainerId]?.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.lockscreenStateAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
+ }
+ }
+
+ launch {
viewModel.translationY.collect { y ->
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.translationY = y
+ childViews[burnInLayerId]?.translationY = y
}
}
- }
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
viewModel.translationX.collect { x ->
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.translationX = x
+ childViews[burnInLayerId]?.translationX = x
}
}
- }
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
viewModel.scale.collect { (scale, scaleClockOnly) ->
if (scaleClockOnly) {
- val largeClock =
- view.findViewById<View?>(R.id.lockscreen_clock_view_large)
- largeClock?.let {
+ childViews[largeClockId]?.let {
it.scaleX = scale
it.scaleY = scale
}
} else {
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.scaleX = scale
- burnInLayer.scaleY = scale
+ childViews[burnInLayerId]?.scaleX = scale
+ childViews[burnInLayerId]?.scaleY = scale
+ }
+ }
+ }
+
+ interactionJankMonitor?.let { jankMonitor ->
+ launch {
+ viewModel.goneToAodTransition.collect {
+ when (it.transitionState) {
+ TransitionState.STARTED -> {
+ val clockId =
+ clockControllerProvider?.get()?.config?.id
+ ?: MISSING_CLOCK_ID
+ val builder =
+ InteractionJankMonitor.Configuration.Builder
+ .withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
+ .setTag(clockId)
+
+ jankMonitor.begin(builder)
+ }
+ TransitionState.CANCELED ->
+ jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.FINISHED ->
+ jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.RUNNING -> Unit
+ }
}
}
}
@@ -132,54 +178,32 @@ object KeyguardRootViewBinder {
}
}
}
-
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- launch {
- viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
- view.animate().cancel()
- val goingToFullShade = visibilityState.goingToFullShade
- val statusBarState = visibilityState.statusBarState
- val isOcclusionTransitionRunning =
- visibilityState.occlusionTransitionRunning
- if (goingToFullShade) {
- view
- .animate()
- .alpha(0f)
- .setStartDelay(
- keyguardStateController.keyguardFadingAwayDelay
- )
- .setDuration(
- keyguardStateController.shortenedFadingAwayDuration
- )
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction { view.visibility = View.GONE }
- .start()
- } else if (
- statusBarState == StatusBarState.KEYGUARD ||
- statusBarState == StatusBarState.SHADE_LOCKED
- ) {
- view.visibility = View.VISIBLE
- if (!isOcclusionTransitionRunning) {
- view.alpha = 1f
- }
- } else {
- view.visibility = View.GONE
- }
- }
- }
- }
- }
}
viewModel.clockControllerProvider = clockControllerProvider
onLayoutChangeListener = OnLayoutChange(viewModel)
view.addOnLayoutChangeListener(onLayoutChangeListener)
+ // Views will be added or removed after the call to bind(). This is needed to avoid many
+ // calls to findViewById
+ view.setOnHierarchyChangeListener(
+ object : OnHierarchyChangeListener {
+ override fun onChildViewAdded(parent: View, child: View) {
+ childViews.put(child.id, view)
+ }
+
+ override fun onChildViewRemoved(parent: View, child: View) {
+ childViews.remove(child.id)
+ }
+ }
+ )
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
view.removeOnLayoutChangeListener(onLayoutChangeListener)
+ view.setOnHierarchyChangeListener(null)
+ childViews.clear()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
index f3586bae1a9e..2feaa2e81c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/**
@@ -46,8 +47,8 @@ class PreviewKeyguardBlueprintViewBinder {
constraintLayout: ConstraintLayout,
viewModel: KeyguardBlueprintViewModel,
finishedAddViewCallback: () -> Unit
- ) {
- constraintLayout.repeatWhenAttached {
+ ): DisposableHandle {
+ return constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
new file mode 100644
index 000000000000..1acea5cfc579
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.util.kotlin.Quint
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewBinder
+@Inject
+constructor(
+ private val viewModel: SideFpsProgressBarViewModel,
+ private val view: SideFpsProgressBar,
+ @Application private val applicationScope: CoroutineScope,
+ private val sfpsController: dagger.Lazy<SideFpsController>,
+) : CoreStartable {
+
+ override fun start() {
+ applicationScope.launch {
+ viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+ if (enabled) {
+ launch {
+ combine(
+ viewModel.isVisible,
+ viewModel.sensorLocation,
+ viewModel.shouldRotate90Degrees,
+ viewModel.isFingerprintAuthRunning,
+ viewModel.sensorWidth,
+ ::Quint
+ )
+ .collectLatest {
+ (visible, location, shouldRotate, fpDetectRunning, sensorWidth) ->
+ view.updateView(visible, location, shouldRotate, sensorWidth)
+ // We have to hide the SFPS indicator as the progress bar will
+ // be shown at the same location
+ if (visible) {
+ sfpsController.get().hideIndicator()
+ } else if (fpDetectRunning) {
+ sfpsController.get().showIndicator()
+ }
+ }
+ }
+ launch { viewModel.progress.collectLatest { view.setProgress(it) } }
+ } else {
+ view.hideOverlay()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4a2954dc6559..5a4bbef587af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -27,6 +27,7 @@ import android.hardware.display.DisplayManager
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
+import android.view.ContextThemeWrapper
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DisplayInfo
@@ -179,7 +180,11 @@ constructor(
fun render() {
mainHandler.post {
- val previewContext = display?.let { context.createDisplayContext(it) } ?: context
+ val previewContext =
+ display?.let {
+ ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
+ }
+ ?: context
val rootView = FrameLayout(previewContext)
@@ -322,7 +327,7 @@ constructor(
@OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
- val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() }
+ val keyguardRootView = KeyguardRootView(previewContext, null)
disposables.add(
KeyguardRootViewBinder.bind(
keyguardRootView,
@@ -333,6 +338,7 @@ constructor(
keyguardStateController,
shadeInteractor,
null, // clock provider only needed for burn in
+ null, // jank monitor not required for preview mode
)
)
rootView.addView(
@@ -342,26 +348,29 @@ constructor(
FrameLayout.LayoutParams.MATCH_PARENT,
),
)
- PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setupShortcuts(keyguardRootView)
- }
- setUpUdfps(previewContext, rootView)
-
- if (!shouldHideClock) {
- setUpClock(previewContext, rootView)
- KeyguardPreviewClockViewBinder.bind(
- largeClockHostView,
- smallClockHostView,
- clockViewModel,
- )
- }
- setUpSmartspace(previewContext, rootView)
- smartSpaceView?.let {
- KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ disposables.add(
+ PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ setupShortcuts(keyguardRootView)
+ }
+ setUpUdfps(previewContext, rootView)
+
+ if (!shouldHideClock) {
+ setUpClock(previewContext, rootView)
+ KeyguardPreviewClockViewBinder.bind(
+ largeClockHostView,
+ smallClockHostView,
+ clockViewModel,
+ )
+ }
+
+ setUpSmartspace(previewContext, rootView)
+ smartSpaceView?.let {
+ KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ }
}
- }
+ )
}
private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
new file mode 100644
index 000000000000..f7ab1ee77582
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ProgressBar
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+private const val TAG = "SideFpsProgressBar"
+
+const val progressBarHeight = 100
+
+@SysUISingleton
+class SideFpsProgressBar
+@Inject
+constructor(
+ private val layoutInflater: LayoutInflater,
+ private val windowManager: WindowManager,
+) {
+ private var progressBarWidth = 200
+ fun updateView(
+ visible: Boolean,
+ location: Point,
+ shouldRotate90Degrees: Boolean,
+ progressBarWidth: Int
+ ) {
+ if (visible) {
+ this.progressBarWidth = progressBarWidth
+ createAndShowOverlay(location, shouldRotate90Degrees)
+ } else {
+ hideOverlay()
+ }
+ }
+
+ fun hideOverlay() {
+ overlayView = null
+ }
+
+ private val overlayViewParams =
+ WindowManager.LayoutParams(
+ progressBarHeight,
+ progressBarWidth,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+ PixelFormat.TRANSPARENT
+ )
+ .apply {
+ title = TAG
+ fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+ gravity = Gravity.TOP or Gravity.LEFT
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+ }
+
+ private var overlayView: View? = null
+ set(value) {
+ field?.let { oldView -> windowManager.removeView(oldView) }
+ field = value
+ field?.let { newView -> windowManager.addView(newView, overlayViewParams) }
+ }
+
+ private fun createAndShowOverlay(
+ fingerprintSensorLocation: Point,
+ shouldRotate90Degrees: Boolean
+ ) {
+ if (overlayView == null) {
+ overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+ }
+ overlayViewParams.x = fingerprintSensorLocation.x
+ overlayViewParams.y = fingerprintSensorLocation.y
+ if (shouldRotate90Degrees) {
+ overlayView?.rotation = 270.0f
+ overlayViewParams.width = progressBarHeight
+ overlayViewParams.height = progressBarWidth
+ } else {
+ overlayView?.rotation = 0.0f
+ overlayViewParams.width = progressBarWidth
+ overlayViewParams.height = progressBarHeight
+ }
+ windowManager.updateViewLayout(overlayView, overlayViewParams)
+ }
+
+ fun setProgress(value: Float) {
+ overlayView
+ ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
+ ?.setProgress((value * 100).toInt(), false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
new file mode 100644
index 000000000000..024707ad2885
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class AodToLockscreenTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.aodToLockscreenTransition,
+ )
+
+ /** Ensure alpha is set to be visible */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStart = { 1f },
+ onStep = { 1f },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
new file mode 100644
index 000000000000..601dbccb1de1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class GoneToAodTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_AOD_DURATION,
+ transitionFlow = interactor.goneToAodTransition,
+ )
+
+ /** y-translation from the top of the screen for AOD */
+ fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
+ return transitionAnimation.createFlow(
+ startTime = 600.milliseconds,
+ duration = 500.milliseconds,
+ onStart = { translatePx },
+ onStep = { translatePx + it * -translatePx },
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_DECELERATE,
+ )
+ }
+
+ /** alpha animation upon entering AOD */
+ val enterFromTopAnimationAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 600.milliseconds,
+ duration = 500.milliseconds,
+ onStart = { 0f },
+ onStep = { it },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 89835fecd95c..1f98082c4065 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,14 +17,19 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.Context
import android.util.MathUtils
+import android.view.View.VISIBLE
import com.android.app.animation.Interpolators
import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,17 +37,23 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardRootViewModel
@Inject
constructor(
+ private val context: Context,
private val keyguardInteractor: KeyguardInteractor,
private val burnInInteractor: BurnInInteractor,
+ private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -56,9 +67,12 @@ constructor(
public var clockControllerProvider: Provider<ClockController>? = null
- /** Represents the current state of the KeyguardRootView visibility */
- val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
- keyguardInteractor.keyguardRootViewVisibilityState
+ val burnInLayerVisibility: Flow<Int> =
+ keyguardTransitionInteractor.startedKeyguardState
+ .filter { it == AOD || it == LOCKSCREEN }
+ .map { VISIBLE }
+
+ val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
@@ -70,9 +84,14 @@ constructor(
}
}
- private val burnIn: Flow<BurnInModel> =
- combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn
- ->
+ private fun burnIn(): Flow<BurnInModel> {
+ val dozingAmount: Flow<Float> =
+ merge(
+ keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+ )
+
+ return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
val useScaleOnly =
clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false
@@ -91,13 +110,61 @@ constructor(
)
}
}
+ }
+
+ /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
+ val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
+
+ /** For elements that appear and move during the animation -> AOD */
+ val burnInLayerAlpha: Flow<Float> =
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(1f)
+ } else {
+ goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+ }
+ }
val translationY: Flow<Float> =
- merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() })
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(0f)
+ } else {
+ keyguardInteractor.configurationChange.flatMapLatest { _ ->
+ val enterFromTopAmount =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_enter_from_top_translation_y
+ )
+ combine(
+ keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+ burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
+ goneToAodTransitionViewModel
+ .enterFromTopTranslationY(enterFromTopAmount)
+ .onStart { emit(0f) },
+ ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY ->
+ // All 3 values need to be combined for a smooth translation
+ keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY
+ }
+ }
+ }
+ }
- val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() }
+ val translationX: Flow<Float> =
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(0f)
+ } else {
+ burnIn().map { it.translationX.toFloat() }
+ }
+ }
- val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) }
+ val scale: Flow<Pair<Float, Boolean>> =
+ previewMode.flatMapLatest { previewMode ->
+ burnIn().map {
+ val scale = if (previewMode.isInPreviewMode) 1f else it.scale
+ Pair(scale, it.scaleClockOnly)
+ }
+ }
/**
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
@@ -105,11 +172,14 @@ constructor(
* lock screen.
*/
fun enablePreviewMode() {
- val newPreviewMode = PreviewMode(true)
- previewMode.value = newPreviewMode
+ previewMode.value = PreviewMode(true)
}
fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) {
+ // Notifications should not be visible in preview mode
+ if (previewMode.value.isInPreviewMode) {
+ return
+ }
keyguardInteractor.sharedNotificationContainerPosition.value =
SharedNotificationContainerPosition(top, bottom)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
new file mode 100644
index 000000000000..2c3b431715a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.animation.ValueAnimator
+import android.graphics.Point
+import androidx.core.animation.doOnEnd
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewModel
+@Inject
+constructor(
+ private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val sfpsSensorInteractor: SideFpsSensorInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ private val _progress = MutableStateFlow(0.0f)
+ private val _visible = MutableStateFlow(false)
+ private var _animator: ValueAnimator? = null
+
+ private fun onFingerprintCaptureCompleted() {
+ _visible.value = false
+ _progress.value = 0.0f
+ }
+
+ val isVisible: Flow<Boolean> = _visible.asStateFlow()
+
+ val progress: Flow<Float> = _progress.asStateFlow()
+
+ val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+
+ val sensorLocation: Flow<Point> =
+ sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+
+ val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+
+ val shouldRotate90Degrees: Flow<Boolean> =
+ combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+ .map { (rotation, sensorLocation) ->
+ if (rotation.isDefaultOrientation()) {
+ sensorLocation.isSensorVerticalInDefaultOrientation
+ } else {
+ !sensorLocation.isSensorVerticalInDefaultOrientation
+ }
+ }
+
+ val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+ sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
+
+ init {
+ applicationScope.launch {
+ combine(
+ sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication,
+ sfpsSensorInteractor.authenticationDuration,
+ ::Pair
+ )
+ .collectLatest { (enabled, authDuration) ->
+ if (!enabled) return@collectLatest
+
+ launch {
+ fpAuthRepository.authenticationStatus.collectLatest { authStatus ->
+ when (authStatus) {
+ is AcquiredFingerprintAuthenticationStatus -> {
+ if (authStatus.fingerprintCaptureStarted) {
+
+ _visible.value = true
+ _animator?.cancel()
+ _animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f)
+ .setDuration(authDuration)
+ .apply {
+ addUpdateListener {
+ _progress.value = it.animatedValue as Float
+ }
+ addListener(
+ doOnEnd {
+ if (_progress.value == 0.0f) {
+ _visible.value = false
+ }
+ }
+ )
+ }
+ _animator?.start()
+ } else if (authStatus.fingerprintCaptureCompleted) {
+ onFingerprintCaptureCompleted()
+ } else {
+ // Abandoned FP Auth attempt
+ _animator?.reverse()
+ }
+ }
+ is ErrorFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is FailFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is SuccessFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ else -> Unit
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
new file mode 100644
index 000000000000..8634b0911391
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.os.Process
+import android.os.RemoteException
+import android.util.Log
+import com.android.internal.util.FrameworkStatsLog
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Helper class for requesting that the server emit logs describing the MediaProjection setup
+ * experience.
+ */
+@SysUISingleton
+class MediaProjectionMetricsLogger
+@Inject
+constructor(private val service: IMediaProjectionManager) {
+ /**
+ * Request to log that the permission was requested.
+ *
+ * @param sessionCreationSource The entry point requesting permission to capture.
+ */
+ fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) {
+ // TODO check that state & SessionCreationSource matches expected values
+ notifyToServer(state, sessionCreationSource)
+ }
+
+ /**
+ * Request to log that the permission request moved to the given state.
+ *
+ * Should not be used for the initialization state, since that
+ */
+ fun notifyPermissionProgress(state: Int) {
+ // TODO validate state is valid
+ notifyToServer(
+ state,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN)
+ }
+
+ /**
+ * Notifies system server that we are handling a particular state during the consent flow.
+ *
+ * Only used for emitting atoms.
+ *
+ * @param state The state that SystemUI is handling during the consent flow. Must be a valid
+ * state defined in the MediaProjectionState enum.
+ * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+ * Indicates the entry point for requesting the permission. Must be a valid state defined in
+ * the SessionCreationSource enum.
+ */
+ private fun notifyToServer(state: Int, sessionCreationSource: Int) {
+ Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
+ try {
+ service.notifyPermissionRequestStateChange(
+ Process.myUid(), state, sessionCreationSource)
+ } catch (e: RemoteException) {
+ Log.e(
+ TAG,
+ "Error notifying server of permission flow state $state from source $sessionCreationSource",
+ e)
+ }
+ }
+
+ companion object {
+ const val TAG = "MediaProjectionMetricsLogger"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
index 2f10ad3e6486..b9bafd4926fa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt
@@ -89,15 +89,15 @@ class MediaProjectionPermissionDialog(
}
return listOf(
ScreenShareOption(
- mode = ENTIRE_SCREEN,
- spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
- warningText = entireScreenWarningText
- ),
- ScreenShareOption(
mode = SINGLE_APP,
spinnerText = R.string.screen_share_permission_dialog_option_single_app,
warningText = singleAppWarningText,
spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+ warningText = entireScreenWarningText
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index 37e8d9f26ee3..9bd57832c4df 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -23,8 +23,8 @@ import kotlin.annotation.Retention
@IntDef(ENTIRE_SCREEN, SINGLE_APP)
annotation class ScreenShareMode
-const val ENTIRE_SCREEN = 0
-const val SINGLE_APP = 1
+const val SINGLE_APP = 0
+const val ENTIRE_SCREEN = 1
class ScreenShareOption(
@ScreenShareMode val mode: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index c4749e093854..c77f3f49a77c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -231,7 +231,6 @@ internal constructor(
animation.removeEndListener(this)
if (!canceled) {
-
// The delay between finishing this animation and starting the runnable
val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
@@ -461,7 +460,6 @@ internal constructor(
}
private fun handleMoveEvent(event: MotionEvent) {
-
val x = event.x
val y = event.y
@@ -927,17 +925,7 @@ internal constructor(
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
updateRestingArrowDimens()
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
- )
- } else {
- vibratorHelper.cancel()
- mainHandler.postDelayed(10L) {
- vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
- }
- }
+ performActivatedHapticFeedback()
val popVelocity =
if (previousState == GestureState.INACTIVE) {
POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
@@ -958,25 +946,24 @@ internal constructor(
mView.popOffEdge(POP_ON_INACTIVE_VELOCITY)
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
- )
- } else {
- vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
- }
+ performDeactivatedHapticFeedback()
updateRestingArrowDimens()
}
GestureState.FLUNG -> {
+ // Typically a vibration is only played while transitioning to ACTIVE. However there
+ // are instances where a fling to trigger back occurs while not in that state.
+ // (e.g. A fling is detected before crossing the trigger threshold.)
+ if (previousState != GestureState.ACTIVE) {
+ performActivatedHapticFeedback()
+ }
mainHandler.postDelayed(POP_ON_FLING_DELAY) {
mView.popScale(POP_ON_FLING_VELOCITY)
}
- updateRestingArrowDimens()
mainHandler.postDelayed(
onEndSetCommittedStateListener.runnable,
MIN_DURATION_FLING_ANIMATION
)
+ updateRestingArrowDimens()
}
GestureState.COMMITTED -> {
// In most cases, animating between states is handled via `updateRestingArrowDimens`
@@ -1011,6 +998,31 @@ internal constructor(
}
}
+ private fun performDeactivatedHapticFeedback() {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ vibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+ )
+ } else {
+ vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+ }
+ }
+
+ private fun performActivatedHapticFeedback() {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ vibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+ )
+ } else {
+ vibratorHelper.cancel()
+ mainHandler.postDelayed(10L) {
+ vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+ }
+ }
+ }
+
private fun convertVelocityToAnimationFactor(
valueOnFastVelocity: Float,
valueOnSlowVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5c6e9028545a..a69820868838 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,6 @@ import com.android.internal.app.MediaRouteDialogPresenter;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -53,12 +52,13 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -85,10 +85,9 @@ public class CastTile extends QSTileImpl<BooleanState> {
private final NetworkController mNetworkController;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final Callback mCallback = new Callback();
- private final WifiInteractor mWifiInteractor;
private final TileJavaAdapter mJavaAdapter;
private final FeatureFlags mFeatureFlags;
- private boolean mWifiConnected;
+ private boolean mCastTransportAllowed;
private boolean mHotspotConnected;
@Inject
@@ -107,7 +106,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
NetworkController networkController,
HotspotController hotspotController,
DialogLaunchAnimator dialogLaunchAnimator,
- WifiInteractor wifiInteractor,
+ ConnectivityRepository connectivityRepository,
TileJavaAdapter javaAdapter,
FeatureFlags featureFlags
) {
@@ -117,7 +116,6 @@ public class CastTile extends QSTileImpl<BooleanState> {
mKeyguard = keyguardStateController;
mNetworkController = networkController;
mDialogLaunchAnimator = dialogLaunchAnimator;
- mWifiInteractor = wifiInteractor;
mJavaAdapter = javaAdapter;
mFeatureFlags = featureFlags;
mController.observe(this, mCallback);
@@ -125,7 +123,11 @@ public class CastTile extends QSTileImpl<BooleanState> {
if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
mNetworkController.observe(this, mSignalCallback);
} else {
- mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+ mJavaAdapter.bind(
+ this,
+ connectivityRepository.getDefaultConnections(),
+ mNetworkModelConsumer
+ );
}
hotspotController.observe(this, mHotspotCallback);
}
@@ -282,7 +284,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
}
state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
: R.drawable.ic_cast);
- if (canCastToWifi() || state.value) {
+ if (canCastToNetwork() || state.value) {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
if (!state.value) {
state.secondaryLabel = "";
@@ -291,7 +293,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
state.forceExpandIcon = willPopDialog();
} else {
state.state = Tile.STATE_UNAVAILABLE;
- String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
+ String noWifi = mContext.getString(R.string.quick_settings_cast_no_network);
state.secondaryLabel = noWifi;
state.forceExpandIcon = false;
}
@@ -308,13 +310,13 @@ public class CastTile extends QSTileImpl<BooleanState> {
: mContext.getString(R.string.quick_settings_cast_device_default_name);
}
- private boolean canCastToWifi() {
- return mWifiConnected || mHotspotConnected;
+ private boolean canCastToNetwork() {
+ return mCastTransportAllowed || mHotspotConnected;
}
- private void setWifiConnected(boolean connected) {
- if (connected != mWifiConnected) {
- mWifiConnected = connected;
+ private void setCastTransportAllowed(boolean connected) {
+ if (connected != mCastTransportAllowed) {
+ mCastTransportAllowed = connected;
// Hotspot is not connected, so changes here should update
if (!mHotspotConnected) {
refreshState();
@@ -326,14 +328,17 @@ public class CastTile extends QSTileImpl<BooleanState> {
if (connected != mHotspotConnected) {
mHotspotConnected = connected;
// Wifi is not connected, so changes here should update
- if (!mWifiConnected) {
+ if (!mCastTransportAllowed) {
refreshState();
}
}
}
- private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
- setWifiConnected(model instanceof WifiNetworkModel.Active);
+ private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> {
+ boolean isWifiDefault = model.getWifi().isDefault();
+ boolean isEthernetDefault = model.getEthernet().isDefault();
+ boolean hasCellularTransport = model.getMobile().isDefault();
+ setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport);
};
private final SignalCallback mSignalCallback = new SignalCallback() {
@@ -342,7 +347,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
// statusIcon.visible has the connected status information
boolean enabledAndConnected = indicators.enabled
&& (indicators.qsIcon != null && indicators.qsIcon.visible);
- setWifiConnected(enabledAndConnected);
+ setCastTransportAllowed(enabledAndConnected);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index a1d5d98ba9e9..ade93b1a913e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -186,14 +186,14 @@ class ScreenRecordPermissionDialog(
private fun createOptionList(): List<ScreenShareOption> {
return listOf(
ScreenShareOption(
- ENTIRE_SCREEN,
- R.string.screen_share_permission_dialog_option_entire_screen,
- R.string.screenrecord_permission_dialog_warning_entire_screen
- ),
- ScreenShareOption(
SINGLE_APP,
R.string.screen_share_permission_dialog_option_single_app,
R.string.screenrecord_permission_dialog_warning_single_app
+ ),
+ ScreenShareOption(
+ ENTIRE_SCREEN,
+ R.string.screen_share_permission_dialog_option_entire_screen,
+ R.string.screenrecord_permission_dialog_warning_entire_screen
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 51540673b9e2..c34fd42e2154 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,11 +20,10 @@ import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
@@ -36,16 +35,15 @@ interface ScreenshotRequestProcessor {
suspend fun process(screenshot: ScreenshotData): ScreenshotData
}
-/**
- * Implementation of [ScreenshotRequestProcessor]
- */
+/** Implementation of [ScreenshotRequestProcessor] */
@SysUISingleton
-class RequestProcessor @Inject constructor(
- private val capture: ImageCapture,
- private val policy: ScreenshotPolicy,
- private val flags: FeatureFlags,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
+class RequestProcessor
+@Inject
+constructor(
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -67,8 +65,9 @@ class RequestProcessor @Inject constructor(
result.userHandle = info.user
if (policy.isManagedProfile(info.user.identifier)) {
- val image = capture.captureTask(info.taskId)
- ?: error("Task snapshot returned a null Bitmap!")
+ val image =
+ capture.captureTask(info.taskId)
+ ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
// Provide the task snapshot as the screenshot
result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -97,3 +96,6 @@ class RequestProcessor @Inject constructor(
}
private const val TAG = "RequestProcessor"
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 4b3bd0b44bc9..21a08a9a4980 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -303,6 +303,13 @@ public class ScreenshotController {
private String mPackageName = "";
private BroadcastReceiver mCopyBroadcastReceiver;
+ // When false, the screenshot is taken without showing the ui. Note that this only applies to
+ // external displays, as on the default one the UI should **always** be shown.
+ // This is needed in case of screenshot during display mirroring, as adding another window to
+ // the external display makes mirroring stop.
+ // When there is a way to distinguish between displays that are mirroring or extending, this
+ // can be removed and we can directly show the ui only in the extended case.
+ private final Boolean mShowUIOnExternalDisplay;
/** Tracks config changes that require re-creating UI */
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_ORIENTATION
@@ -318,7 +325,7 @@ public class ScreenshotController {
Context context,
FeatureFlags flags,
ScreenshotSmartActions screenshotSmartActions,
- ScreenshotNotificationsController screenshotNotificationsController,
+ ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
@@ -335,10 +342,11 @@ public class ScreenshotController {
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
Provider<ScreenshotSoundController> screenshotSoundController,
- @Assisted int displayId
+ @Assisted int displayId,
+ @Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
- mNotificationsController = screenshotNotificationsController;
+ mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
@@ -401,6 +409,7 @@ public class ScreenshotController {
mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
ClipboardOverlayController.COPY_OVERLAY_ACTION),
ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+ mShowUIOnExternalDisplay = showUIOnExternalDisplay;
}
void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
@@ -448,6 +457,23 @@ public class ScreenshotController {
prepareViewForNewScreenshot(screenshot, oldPackageName);
+ if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+ new AssistContentRequester.Callback() {
+ @Override
+ public void onAssistContentAvailable(AssistContent assistContent) {
+ screenshot.setContextUrl(assistContent.getWebUri());
+ }
+ });
+ }
+
+ if (!shouldShowUi()) {
+ saveScreenshotInWorkerThread(
+ screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
+ (ignored) -> {});
+ return;
+ }
+
saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
@@ -482,22 +508,16 @@ public class ScreenshotController {
screenshot.getUserHandle()));
mScreenshotView.setScreenshot(screenshot);
- if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
- mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
- new AssistContentRequester.Callback() {
- @Override
- public void onAssistContentAvailable(AssistContent assistContent) {
- screenshot.setContextUrl(assistContent.getWebUri());
- }
- });
- }
-
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
mScreenshotHandler.cancelTimeout(); // restarted after animation
}
+ private boolean shouldShowUi() {
+ return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay;
+ }
+
void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
@@ -1199,7 +1219,13 @@ public class ScreenshotController {
/** Injectable factory to create screenshot controller instances for a specific display. */
@AssistedFactory
public interface Factory {
- /** Creates an instance of the controller for that specific displayId. */
- ScreenshotController create(int displayId);
+ /**
+ * Creates an instance of the controller for that specific displayId.
+ *
+ * @param displayId: display to capture
+ * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external
+ * display.
+ */
+ ScreenshotController create(int displayId, boolean showUIOnExternalDisplay);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
deleted file mode 100644
index 4344fd1a7567..000000000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.Context.NOTIFICATION_SERVICE;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.res.R;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.util.NotificationChannels;
-
-import javax.inject.Inject;
-
-/**
- * Convenience class to handle showing and hiding notifications while taking a screenshot.
- */
-public class ScreenshotNotificationsController {
- private static final String TAG = "ScreenshotNotificationManager";
-
- private final Context mContext;
- private final Resources mResources;
- private final NotificationManager mNotificationManager;
-
- @Inject
- ScreenshotNotificationsController(Context context, WindowManager windowManager) {
- mContext = context;
- mResources = context.getResources();
- mNotificationManager =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
- }
-
- /**
- * Sends a notification that the screenshot capture has failed.
- */
- public void notifyScreenshotError(int msgResId) {
- Resources res = mContext.getResources();
- String errorMsg = res.getString(msgResId);
-
- // Repurpose the existing notification to notify the user of the error
- Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
- .setTicker(res.getString(R.string.screenshot_failed_title))
- .setContentTitle(res.getString(R.string.screenshot_failed_title))
- .setContentText(errorMsg)
- .setSmallIcon(R.drawable.stat_notify_image_error)
- .setWhen(System.currentTimeMillis())
- .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
- .setCategory(Notification.CATEGORY_ERROR)
- .setAutoCancel(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- final Intent intent =
- dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
- if (intent != null) {
- final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
- b.setContentIntent(pendingIntent);
- }
-
- SystemUIApplication.overrideNotificationAppName(mContext, b, true);
-
- Notification n = new Notification.BigTextStyle(b)
- .bigText(errorMsg)
- .build();
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
new file mode 100644
index 000000000000..d874eb68460b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserHandle
+import android.view.Display
+import com.android.internal.R
+import com.android.internal.messages.nano.SystemMessageProto
+import com.android.systemui.SystemUIApplication
+import com.android.systemui.util.NotificationChannels
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Convenience class to handle showing and hiding notifications while taking a screenshot. */
+class ScreenshotNotificationsController
+@AssistedInject
+internal constructor(
+ @Assisted private val displayId: Int,
+ private val context: Context,
+ private val notificationManager: NotificationManager,
+ private val devicePolicyManager: DevicePolicyManager,
+) {
+ private val res = context.resources
+
+ /**
+ * Sends a notification that the screenshot capture has failed.
+ *
+ * Errors for the non-default display are shown in a unique separate notification.
+ */
+ fun notifyScreenshotError(msgResId: Int) {
+ val displayErrorString =
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ " ($externalDisplayString)"
+ } else {
+ ""
+ }
+ val errorMsg = res.getString(msgResId) + displayErrorString
+
+ // Repurpose the existing notification or create a new one
+ val builder =
+ Notification.Builder(context, NotificationChannels.ALERTS)
+ .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title))
+ .setContentTitle(
+ res.getString(com.android.systemui.res.R.string.screenshot_failed_title)
+ )
+ .setContentText(errorMsg)
+ .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error)
+ .setWhen(System.currentTimeMillis())
+ .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+ .setCategory(Notification.CATEGORY_ERROR)
+ .setAutoCancel(true)
+ .setColor(context.getColor(R.color.system_notification_accent_color))
+ val intent =
+ devicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE
+ )
+ if (intent != null) {
+ val pendingIntent =
+ PendingIntent.getActivityAsUser(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE,
+ null,
+ UserHandle.CURRENT
+ )
+ builder.setContentIntent(pendingIntent)
+ }
+ SystemUIApplication.overrideNotificationAppName(context, builder, true)
+ val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build()
+ // A different id for external displays to keep the 2 error notifications separated.
+ val id =
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT
+ } else {
+ SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY
+ }
+ notificationManager.notify(id, notification)
+ }
+
+ private val externalDisplayString: String
+ get() =
+ res.getString(
+ com.android.systemui.res.R.string.screenshot_failed_external_display_indication
+ )
+
+ /** Factory for [ScreenshotNotificationsController]. */
+ @AssistedFactory
+ interface Factory {
+ fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index 070fb1eef99a..049799e96c53 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,25 +16,29 @@
package com.android.systemui.screenshot;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.view.WindowManager;
+import android.view.Display;
import com.android.systemui.res.R;
/**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
+ * Receives errors related to screenshot.
*/
public class ScreenshotServiceErrorReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
// Show a message that we've failed to save the image to disk
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- ScreenshotNotificationsController controller =
- new ScreenshotNotificationsController(context, wm);
+ NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
+ DevicePolicyManager devicePolicyManager = context.getSystemService(
+ DevicePolicyManager.class);
+ ScreenshotNotificationsController controller = new ScreenshotNotificationsController(
+ Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager);
controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 03c3f7a8ddf3..015828438375 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -10,6 +10,8 @@ import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import java.util.function.Consumer
import javax.inject.Inject
@@ -34,7 +36,8 @@ constructor(
displayRepository: DisplayRepository,
@Application private val mainScope: CoroutineScope,
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
- private val uiEventLogger: UiEventLogger
+ private val uiEventLogger: UiEventLogger,
+ private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
) {
private lateinit var displays: StateFlow<Set<Display>>
@@ -44,6 +47,7 @@ constructor(
}
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+ private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
/**
* Executes the [ScreenshotRequest].
@@ -58,40 +62,68 @@ constructor(
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
- screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
+ displayIds.forEach { displayId: Int ->
dispatchToController(
- screenshotData = screenshotData,
+ rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
onSaved =
- if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> },
- callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId)
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ onSaved
+ } else { _ -> },
+ callback = resultCallbackWrapper.createCallbackForId(displayId)
)
}
}
- /** Creates a [ScreenshotData] for each display. */
- private suspend fun ScreenshotRequest.oneForEachDisplay(
- displayIds: List<Int>
- ): List<ScreenshotData> {
- return displayIds
- .map { displayId -> ScreenshotData.fromRequest(this, displayId) }
- .map { screenshotData: ScreenshotData ->
- screenshotRequestProcessor.process(screenshotData)
- }
- }
-
- private fun dispatchToController(
- screenshotData: ScreenshotData,
+ /** All logging should be triggered only by this method. */
+ private suspend fun dispatchToController(
+ rawScreenshotData: ScreenshotData,
onSaved: (Uri) -> Unit,
callback: RequestCallback
) {
+ // Let's wait before logging "screenshot requested", as we should log the processed
+ // ScreenshotData.
+ val screenshotData =
+ try {
+ screenshotRequestProcessor.process(rawScreenshotData)
+ } catch (e: RequestProcessorException) {
+ Log.e(TAG, "Failed to process screenshot request!", e)
+ logScreenshotRequested(rawScreenshotData)
+ onFailedScreenshotRequest(rawScreenshotData, callback)
+ return
+ }
+
+ logScreenshotRequested(screenshotData)
+ Log.d(TAG, "Screenshot request: $screenshotData")
+ try {
+ getScreenshotController(screenshotData.displayId)
+ .handleScreenshot(screenshotData, onSaved, callback)
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
+ onFailedScreenshotRequest(screenshotData, callback)
+ return // After a failure log, nothing else should run.
+ }
+ }
+
+ /**
+ * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED]
+ * event.
+ */
+ private fun logScreenshotRequested(screenshotData: ScreenshotData) {
uiEventLogger.log(
ScreenshotEvent.getScreenshotSource(screenshotData.source),
0,
screenshotData.packageNameString
)
- Log.d(TAG, "Screenshot request: $screenshotData")
- getScreenshotController(screenshotData.displayId)
- .handleScreenshot(screenshotData, onSaved, callback)
+ }
+
+ private fun onFailedScreenshotRequest(
+ screenshotData: ScreenshotData,
+ callback: RequestCallback
+ ) {
+ uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString)
+ getNotificationController(screenshotData.displayId)
+ .notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
+ callback.reportError()
}
private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
@@ -135,7 +167,15 @@ constructor(
}
private fun getScreenshotController(id: Int): ScreenshotController {
- return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) }
+ return screenshotControllers.computeIfAbsent(id) {
+ screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false)
+ }
+ }
+
+ private fun getNotificationController(id: Int): ScreenshotNotificationsController {
+ return notificationControllers.computeIfAbsent(id) {
+ screenshotNotificationControllerFactory.create(id)
+ }
}
/** For java compatibility only. see [executeScreenshots] */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0be2265bf8fa..0991c9a326c8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -113,7 +113,8 @@ public class TakeScreenshotService extends Service {
@Inject
public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
UserManager userManager, DevicePolicyManager devicePolicyManager,
- UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController,
+ UiEventLogger uiEventLogger,
+ ScreenshotNotificationsController.Factory notificationsControllerFactory,
Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
if (DEBUG_SERVICE) {
@@ -123,7 +124,7 @@ public class TakeScreenshotService extends Service {
mUserManager = userManager;
mDevicePolicyManager = devicePolicyManager;
mUiEventLogger = uiEventLogger;
- mNotificationsController = notificationsController;
+ mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
mContext = context;
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
@@ -132,7 +133,8 @@ public class TakeScreenshotService extends Service {
if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
mScreenshot = null;
} else {
- mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY);
+ mScreenshot = screenshotControllerFactory.create(
+ Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false);
}
}
@@ -245,15 +247,17 @@ public class TakeScreenshotService extends Service {
Log.d(TAG, "Processing screenshot data");
- ScreenshotData screenshotData = ScreenshotData.fromRequest(
- request, Display.DEFAULT_DISPLAY);
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
+ return;
+ }
+ // TODO(b/295143676): Delete the following after the flag is released.
try {
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
- } else {
- mProcessor.processAsync(screenshotData, (data) ->
- dispatchToController(data, onSaved, callback));
- }
+ ScreenshotData screenshotData = ScreenshotData.fromRequest(
+ request, Display.DEFAULT_DISPLAY);
+ mProcessor.processAsync(screenshotData, (data) ->
+ dispatchToController(data, onSaved, callback));
+
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to process screenshot request!", e);
logFailedRequest(request);
@@ -263,6 +267,7 @@ public class TakeScreenshotService extends Service {
}
}
+ // TODO(b/295143676): Delete this.
private void dispatchToController(ScreenshotData screenshot,
Consumer<Uri> uriConsumer, RequestCallback callback) {
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ba0cf08150f6..8dc97c0f797c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -414,9 +414,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private int mDisplayLeftInset = 0; // in pixels
@VisibleForTesting
- KeyguardClockPositionAlgorithm
- mClockPositionAlgorithm =
- new KeyguardClockPositionAlgorithm();
+ KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private final KeyguardClockPositionAlgorithm.Result
mClockPositionResult =
new KeyguardClockPositionAlgorithm.Result();
@@ -779,7 +777,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
KeyguardViewConfigurator keyguardViewConfigurator,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
SplitShadeStateController splitShadeStateController,
- PowerInteractor powerInteractor) {
+ PowerInteractor powerInteractor,
+ KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -807,6 +806,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
+ mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1446,9 +1446,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mBarState);
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setKeyguardVisibility(mBarState, false);
- } else {
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -2358,14 +2356,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
}
- private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
- mKeyguardInteractor.setKeyguardRootVisibility(
- statusBarState,
- goingToFullShade,
- mIsOcclusionTransitionRunning
- );
- }
-
@Deprecated
private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
mKeyguardBottomArea.animate().cancel();
@@ -4443,11 +4433,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
&& statusBarState == KEYGUARD) {
// This means we're doing the screen off animation - position the keyguard status
// view where it'll be on AOD, so we can animate it in.
- mKeyguardStatusViewController.updatePosition(
- mClockPositionResult.clockX,
- mClockPositionResult.clockYFullyDozing,
- mClockPositionResult.clockScale,
- false /* animate */);
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ mKeyguardStatusViewController.updatePosition(
+ mClockPositionResult.clockX,
+ mClockPositionResult.clockYFullyDozing,
+ mClockPositionResult.clockScale,
+ false /* animate */);
+ }
}
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
@@ -4456,9 +4448,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
goingToFullShade,
mBarState);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setKeyguardVisibility(statusBarState, goingToFullShade);
- } else {
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
}
@@ -4562,7 +4552,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
public void showAodUi() {
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
- mStatusBarStateListener.onStateChanged(KEYGUARD);
+
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ mStatusBarStateController.setState(KEYGUARD);
+ } else {
+ mStatusBarStateListener.onStateChanged(KEYGUARD);
+ }
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
setExpandedFraction(1f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 25bd8e7446bc..cb95b25ece80 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@ import androidx.core.view.doOnLayout
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -49,6 +48,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.ChipVisibilityListener
import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.res.R
import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
@@ -304,7 +304,8 @@ constructor(
iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
- Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+ Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary),
+ Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse),
)
carrierIconSlots =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2147510bbde5..f32f1c2dcd25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,9 +16,16 @@
package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.UserHandle.USER_NULL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -30,8 +37,10 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -41,14 +50,18 @@ import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -63,7 +76,9 @@ import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -84,6 +99,11 @@ public class NotificationLockscreenUserManagerImpl implements
private final SecureSettings mSecureSettings;
private final Object mLock = new Object();
+ private static final Uri SHOW_LOCKSCREEN =
+ Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ private static final Uri SHOW_PRIVATE_LOCKSCREEN =
+ Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;
private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;
private final DevicePolicyManager mDevicePolicyManager;
@@ -91,6 +111,23 @@ public class NotificationLockscreenUserManagerImpl implements
private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
+
+ // The variables between mUsersDpcAllowingNotifications and
+ // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread
+ // and read on the main thread. Because the pipeline needs these values, adding locks would
+ // introduce too much jank. This means that some pipeline runs could get incorrect values, that
+ // would be fixed on the next pipeline run. We think this will be rare since a pipeline run
+ // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the
+ // pipeline frequently enough that it should be corrected by the next time it matters for the
+ // user.
+ private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray();
+ private boolean mKeyguardAllowingNotifications = true;
+ private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications
+ = new SparseBooleanArray();
+ private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications
+ = new SparseBooleanArray();
+
private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
private final UserManager mUserManager;
@@ -99,24 +136,39 @@ public class NotificationLockscreenUserManagerImpl implements
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy;
+ private final FeatureFlagsClassic mFeatureFlags;
private boolean mShowLockscreenNotifications;
private LockPatternUtils mLockPatternUtils;
protected KeyguardManager mKeyguardManager;
private int mState = StatusBarState.SHADE;
private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =
new ListenerSet<>();
+ private final Collection<Uri> mLockScreenUris = new ArrayList<>();
+
protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) &&
- isCurrentProfile(getSendingUserId())) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- // TODO(b/231976036): Consolidate pipeline invalidations related to this event
- // notifyNotificationStateChanged();
+ if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ boolean changed = updateDpcSettings(getSendingUserId());
+ if (mCurrentUserId == getSendingUserId()) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
+ }
+ } else {
+ if (isCurrentProfile(getSendingUserId())) {
+ mUsersAllowingPrivateNotifications.clear();
+ updateLockscreenNotificationSetting();
+ // TODO(b/231976036): Consolidate pipeline invalidations related to this
+ // event
+ // notifyNotificationStateChanged();
+ }
+ }
}
}
};
@@ -136,6 +188,14 @@ public class NotificationLockscreenUserManagerImpl implements
updateCurrentProfilesCache();
break;
case Intent.ACTION_USER_ADDED:
+ updateCurrentProfilesCache();
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mBackgroundHandler.post(() -> {
+ initValuesForUser(userId);
+ });
+ }
+ break;
case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
updateCurrentProfilesCache();
@@ -193,6 +253,8 @@ public class NotificationLockscreenUserManagerImpl implements
protected final Context mContext;
private final Handler mMainHandler;
+ private final Handler mBackgroundHandler;
+ private final Executor mBackgroundExecutor;
protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
@@ -214,13 +276,18 @@ public class NotificationLockscreenUserManagerImpl implements
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
@Main Handler mainHandler,
+ @Background Handler backgroundHandler,
+ @Background Executor backgroundExecutor,
DeviceProvisionedController deviceProvisionedController,
KeyguardStateController keyguardStateController,
SecureSettings secureSettings,
DumpManager dumpManager,
- LockPatternUtils lockPatternUtils) {
+ LockPatternUtils lockPatternUtils,
+ FeatureFlagsClassic featureFlags) {
mContext = context;
mMainHandler = mainHandler;
+ mBackgroundHandler = backgroundHandler;
+ mBackgroundExecutor = backgroundExecutor;
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
mUserTracker = userTracker;
@@ -236,6 +303,10 @@ public class NotificationLockscreenUserManagerImpl implements
mDeviceProvisionedController = deviceProvisionedController;
mSecureSettings = secureSettings;
mKeyguardStateController = keyguardStateController;
+ mFeatureFlags = featureFlags;
+
+ mLockScreenUris.add(SHOW_LOCKSCREEN);
+ mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);
dumpManager.registerDumpable(this);
}
@@ -243,16 +314,53 @@ public class NotificationLockscreenUserManagerImpl implements
public void setUpWithPresenter(NotificationPresenter presenter) {
mPresenter = presenter;
- mLockscreenSettingsObserver = new ContentObserver(mMainHandler) {
+ mLockscreenSettingsObserver = new ContentObserver(
+ mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+ ? mBackgroundHandler
+ : mMainHandler) {
+
@Override
- public void onChange(boolean selfChange) {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
- // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
- mUsersAllowingPrivateNotifications.clear();
- mUsersAllowingNotifications.clear();
- // ... and refresh all the notifications
- updateLockscreenNotificationSetting();
- notifyNotificationStateChanged();
+ public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ @SuppressLint("MissingPermission")
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ onChange(selfChange, uris, flags,users.get(i).getUserHandle());
+ }
+ } else {
+ // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
+ // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
+ mUsersAllowingPrivateNotifications.clear();
+ mUsersAllowingNotifications.clear();
+ // ... and refresh all the notifications
+ updateLockscreenNotificationSetting();
+ notifyNotificationStateChanged();
+ }
+ }
+
+ // Note: even though this is an override, this method is not called by the OS
+ // since we're not in system_server. We are using it internally for cases when
+ // we have a single user id available (e.g. from USER_ADDED).
+ @Override
+ public void onChange(boolean selfChange, Collection<Uri> uris,
+ int flags, UserHandle user) {
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ boolean changed = false;
+ for (Uri uri: uris) {
+ if (SHOW_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowSettings(user.getIdentifier());
+ } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowPrivateSettings(user.getIdentifier());
+ }
+ }
+
+ if (mCurrentUserId == user.getIdentifier()) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
+ }
+ }
}
};
@@ -268,23 +376,26 @@ public class NotificationLockscreenUserManagerImpl implements
};
mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false,
+ SHOW_LOCKSCREEN, false,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
- mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+ SHOW_PRIVATE_LOCKSCREEN,
true,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
- mSettingsObserver);
+ if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+ mSettingsObserver);
+ }
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- null /* handler */, UserHandle.ALL);
+ mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
+ ? mBackgroundExecutor : null, UserHandle.ALL);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
@@ -305,7 +416,23 @@ public class NotificationLockscreenUserManagerImpl implements
mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
- mSettingsObserver.onChange(false); // set up
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ // Set up
+ mBackgroundHandler.post(() -> {
+ @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ initValuesForUser(users.get(i).id);
+ }
+ });
+ } else {
+ mSettingsObserver.onChange(false); // set up
+ }
+ }
+
+ private void initValuesForUser(@UserIdInt int userId) {
+ mLockscreenSettingsObserver.onChange(
+ false, mLockScreenUris, 0, UserHandle.of(userId));
+ updateDpcSettings(userId);
}
public boolean shouldShowLockscreenNotifications() {
@@ -322,17 +449,75 @@ public class NotificationLockscreenUserManagerImpl implements
mShowLockscreenNotifications = show;
}
- protected void updateLockscreenNotificationSetting() {
- final boolean show = mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- 1,
- mCurrentUserId) != 0;
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
- null /* admin */, mCurrentUserId);
- final boolean allowedByDpm = (dpmFlags
- & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ protected boolean updateLockscreenNotificationSetting() {
+ boolean show;
+ boolean allowedByDpm;
+
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+ && mKeyguardAllowingNotifications;
+ // If DPC never notified us about a user, that means they have no policy for the user,
+ // and they allow the behavior
+ allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+ } else {
+ show = mSecureSettings.getIntForUser(
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1,
+ mCurrentUserId) != 0;
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+ null /* admin */, mCurrentUserId);
+ allowedByDpm = (dpmFlags
+ & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ }
+ final boolean oldValue = mShowLockscreenNotifications;
setShowLockscreenNotifications(show && allowedByDpm);
+
+ return oldValue != mShowLockscreenNotifications;
+ }
+
+ @WorkerThread
+ protected boolean updateDpcSettings(int userId) {
+ boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId);
+ boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId);
+ final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
+ null /* admin */, userId);
+ final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
+ mUsersDpcAllowingNotifications.put(userId, allowedLockscreen);
+ mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate);
+ return (originalAllowLockscreen != allowedLockscreen)
+ || (originalAllowPrivate != allowedPrivate);
+ }
+
+ @WorkerThread
+ private boolean updateUserShowSettings(int userId) {
+ boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId);
+ boolean newAllowLockscreen = mSecureSettings.getIntForUser(
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ 1,
+ userId) != 0;
+ mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen);
+ boolean keyguardChanged = updateGlobalKeyguardSettings();
+ return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged;
+ }
+
+ @WorkerThread
+ private boolean updateUserShowPrivateSettings(int userId) {
+ boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId);
+ boolean newValue = mSecureSettings.getIntForUser(
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ 0,
+ userId) != 0;
+ mUsersUsersAllowingPrivateNotifications.put(userId, newValue);
+ return (newValue != originalValue);
+ }
+
+ @WorkerThread
+ private boolean updateGlobalKeyguardSettings() {
+ final boolean oldValue = mKeyguardAllowingNotifications;
+ mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed();
+ return oldValue != mKeyguardAllowingNotifications;
}
/**
@@ -340,21 +525,41 @@ public class NotificationLockscreenUserManagerImpl implements
* when the lockscreen is in "public" (secure & locked) mode?
*/
public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
- if (userHandle == UserHandle.USER_ALL) {
- return true;
- }
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ if (userHandle == UserHandle.USER_ALL) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable());
+ updateUserShowPrivateSettings(userHandle);
+ }
+ if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+ updateDpcSettings(userHandle);
+ }
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle);
+ } else {
+ if (userHandle == UserHandle.USER_ALL) {
+ return true;
+ }
- if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- final boolean allowed = allowedByUser && allowedByDpm;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed);
- return allowed;
- }
+ if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+ final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+ KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+ final boolean allowed = allowedByUser && allowedByDpm;
+ mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+ return allowed;
+ }
- return mUsersAllowingPrivateNotifications.get(userHandle);
+ return mUsersAllowingPrivateNotifications.get(userHandle);
+ }
}
/**
@@ -406,21 +611,44 @@ public class NotificationLockscreenUserManagerImpl implements
* "public" (secure & locked) mode?
*/
public boolean userAllowsNotificationsInPublic(int userHandle) {
- if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
- return true;
- }
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ // Unlike 'show private', settings does not show a copy of this setting for each
+ // profile, so it inherits from the parent user.
+ if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+ updateUserShowSettings(userHandle);
+ }
+ if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+ updateDpcSettings(userHandle);
+ }
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
+ } else {
+ if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
+ return true;
+ }
- if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
- final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
- mUsersAllowingNotifications.append(userHandle, allowed);
- return allowed;
+ if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
+ LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+ final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
+ KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+ final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
+ final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
+ mUsersAllowingNotifications.append(userHandle, allowed);
+ return allowed;
+ }
+ return mUsersAllowingNotifications.get(userHandle);
}
- return mUsersAllowingNotifications.get(userHandle);
}
/** @return true if the entry needs redaction when on the lockscreen. */
@@ -451,9 +679,15 @@ public class NotificationLockscreenUserManagerImpl implements
return true;
}
NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
- return entry != null
- && entry.getRanking().getLockscreenVisibilityOverride()
- == Notification.VISIBILITY_PRIVATE;
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ return entry != null
+ && entry.getRanking().getChannel().getLockscreenVisibility()
+ == Notification.VISIBILITY_PRIVATE;
+ } else {
+ return entry != null
+ && entry.getRanking().getLockscreenVisibilityOverride()
+ == Notification.VISIBILITY_PRIVATE;
+ }
}
private void updateCurrentProfilesCache() {
@@ -491,20 +725,6 @@ public class NotificationLockscreenUserManagerImpl implements
}
/**
- * If any managed/work profiles are in public mode.
- */
- public boolean isAnyManagedProfilePublicMode() {
- synchronized (mLock) {
- for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
- if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
* Returns the current user id. This can change if the user is switched.
*/
public int getCurrentUserId() {
@@ -581,8 +801,16 @@ public class NotificationLockscreenUserManagerImpl implements
}
private void notifyNotificationStateChanged() {
- for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
- listener.onNotificationStateChanged();
+ if (!Looper.getMainLooper().isCurrentThread()) {
+ mMainHandler.post(() -> {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
+ });
+ } else {
+ for (NotificationStateChangedListener listener : mNotifStateChangedListeners) {
+ listener.onNotificationStateChanged();
+ }
}
}
@@ -620,5 +848,15 @@ public class NotificationLockscreenUserManagerImpl implements
pw.println(mUsersInLockdownLatestResult);
pw.print(" mShouldHideNotifsLatestResult=");
pw.println(mShouldHideNotifsLatestResult);
+ pw.print(" mUsersDpcAllowingNotifications=");
+ pw.println(mUsersDpcAllowingNotifications);
+ pw.print(" mUsersUsersAllowingNotifications=");
+ pw.println(mUsersUsersAllowingNotifications);
+ pw.print(" mKeyguardAllowingNotifications=");
+ pw.println(mKeyguardAllowingNotifications);
+ pw.print(" mUsersDpcAllowingPrivateNotifications=");
+ pw.println(mUsersDpcAllowingPrivateNotifications);
+ pw.print(" mUsersUsersAllowingPrivateNotifications=");
+ pw.println(mUsersUsersAllowingPrivateNotifications);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index d4b6dfb9b625..61ebcc0c99d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
@@ -48,10 +49,10 @@ import androidx.annotation.Nullable;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -535,7 +536,8 @@ public class NotificationRemoteInputManager implements Dumpable {
public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {
if (isRemoteInputActive(entry)) {
entry.mRemoteEditImeVisible = false;
- mRemoteInputController.removeRemoteInput(entry, null);
+ mRemoteInputController.removeRemoteInput(entry, null,
+ /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f8c049e86cb8..23b697e5554b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -133,7 +133,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController hostLayoutController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -143,7 +143,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
NotificationRoundnessManager roundnessManager) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mAmbientState = ambientState;
mHostLayout = hostLayout;
mRoundnessManager = roundnessManager;
@@ -964,7 +964,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
@Override
public void onStateChanged(int newState) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mStatusBarState = newState;
updateInteractiveness();
}
@@ -1022,17 +1022,17 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
public void setController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mController = notificationShelfController;
}
public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mCanModifyColorOfNotifications = canModifyColorOfNotifications;
}
public void setCanInteract(boolean canInteract) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mCanInteract = canInteract;
updateInteractiveness();
}
@@ -1050,7 +1050,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
public void requestRoundnessResetFor(ExpandableView child) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
child.requestRoundnessReset(SHELF_SCROLL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index d5e4902366e7..17da015789ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -31,6 +31,8 @@ import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Objects;
@@ -69,7 +71,8 @@ public class RemoteInputController {
* @param entry the entry for which a remote input is now active.
* @param token a token identifying the view that is managing the remote input
*/
- public void addRemoteInput(NotificationEntry entry, Object token) {
+ public void addRemoteInput(NotificationEntry entry, Object token,
+ @CompileTimeConstant String reason) {
Objects.requireNonNull(entry);
Objects.requireNonNull(token);
boolean isActive = isRemoteInputActive(entry);
@@ -77,7 +80,9 @@ public class RemoteInputController {
entry /* contains */, null /* remove */, token /* removeToken */);
mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,
isActive /* isRemoteInputAlreadyActive */,
- found /* isRemoteInputFound */);
+ found /* isRemoteInputFound */,
+ reason /* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
if (!found) {
mOpen.add(new Pair<>(new WeakReference<>(entry), token));
}
@@ -96,7 +101,8 @@ public class RemoteInputController {
* the entry is only removed if the token matches the last added token for this
* entry. If null, the entry is removed regardless.
*/
- public void removeRemoteInput(NotificationEntry entry, Object token) {
+ public void removeRemoteInput(NotificationEntry entry, Object token,
+ @CompileTimeConstant String reason) {
Objects.requireNonNull(entry);
if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
mLogger.logRemoveRemoteInput(
@@ -104,9 +110,12 @@ public class RemoteInputController {
true /* remoteEditImeVisible */,
true /* remoteEditImeAnimatingAway */,
isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */,
- isRemoteInputActive() /* isRemoteInputActive */);
+ isRemoteInputActive() /* isRemoteInputActive */,
+ reason /* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
return;
}
+
// If the view is being removed, this may be called even though we're not active
boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
mLogger.logRemoveRemoteInput(
@@ -114,7 +123,9 @@ public class RemoteInputController {
entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
- isRemoteInputActive()/* isRemoteInputActive */);
+ isRemoteInputActive()/* isRemoteInputActive */,
+ reason/* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
if (!remoteInputActiveForEntry) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 1196211bd671..595ab701a9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -21,6 +21,21 @@ import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
public interface StatusIconDisplayable extends DarkReceiver {
String getSlot();
void setStaticDrawableColor(int color);
+
+ /**
+ * For a layer drawable, or one that has a background, {@code tintColor} should be used as the
+ * background tint for the container, while {@code contrastColor} can be used as the foreground
+ * drawable's tint so that it is visible on the background. Essentially, tintColor should apply
+ * to the portion of the icon that borders the underlying window content (status bar's
+ * background), and the contrastColor only need be used to distinguish from the tintColor.
+ *
+ * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern
+ * callers can just call this method and still get the default behavior.
+ */
+ default void setStaticDrawableColor(int tintColor, int contrastColor) {
+ setStaticDrawableColor(tintColor);
+ }
+
void setDecorColor(int color);
/** Sets the visible state that this displayable should be. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 39b999cb4f35..ff89c62ab496 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -32,17 +32,24 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {
fun logAddRemoteInput(
entryKey: String,
isRemoteInputAlreadyActive: Boolean,
- isRemoteInputFound: Boolean
+ isRemoteInputFound: Boolean,
+ reason: String,
+ notificationStyle: String
) =
logBuffer.log(
TAG,
DEBUG,
{
str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
bool1 = isRemoteInputAlreadyActive
bool2 = isRemoteInputFound
},
- { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" }
+ {
+ "addRemoteInput reason:$str2 entry: $str1, style:$str3" +
+ ", isAlreadyActive: $bool1, isFound:$bool2"
+ }
)
/** logs removeRemoteInput invocation of [RemoteInputController] */
@@ -52,20 +59,25 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {
remoteEditImeVisible: Boolean,
remoteEditImeAnimatingAway: Boolean,
isRemoteInputActiveForEntry: Boolean,
- isRemoteInputActive: Boolean
+ isRemoteInputActive: Boolean,
+ reason: String,
+ notificationStyle: String
) =
logBuffer.log(
TAG,
DEBUG,
{
str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
bool1 = remoteEditImeVisible
bool2 = remoteEditImeAnimatingAway
bool3 = isRemoteInputActiveForEntry
bool4 = isRemoteInputActive
},
{
- "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+ "removeRemoteInput reason: $str2 entry: $str1" +
+ ", style: $str3, remoteEditImeVisible: $bool1" +
", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" +
", isRemoteInputActive: $bool4"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index affd2d186774..4573d5989faa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -979,6 +979,19 @@ public final class NotificationEntry extends ListEntry {
return mExpandAnimationRunning;
}
+ /**
+ * @return NotificationStyle
+ */
+ public String getNotificationStyle() {
+ if (isSummaryWithChildren()) {
+ return "summary";
+ }
+
+ final Class<? extends Notification.Style> style =
+ getSbn().getNotification().getNotificationStyle();
+ return style == null ? "nostyle" : style.getSimpleName();
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index 50efbb5458cf..eb5c1fa3b0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -167,17 +167,16 @@ constructor(
NotificationShelfViewBinderWrapperControllerImpl.unsupported
override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.expectEnabled()) {
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- shelfIcons = icons
- }
+ if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
+ NotificationIconContainerViewBinder.bind(
+ icons,
+ shelfIconsViewModel,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ )
+ shelfIcons = icons
}
override fun onDensityOrFontScaleChanged(context: Context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index b2c32cd42d1d..db7f46eb28f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -1,6 +1,5 @@
package com.android.systemui.statusbar.notification.interruption
-import android.app.Notification
import android.app.Notification.VISIBILITY_SECRET
import android.content.Context
import android.database.ContentObserver
@@ -14,6 +13,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -78,7 +79,8 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val userTracker: UserTracker,
private val secureSettings: SecureSettings,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val featureFlags: FeatureFlagsClassic
) : CoreStartable, KeyguardNotificationVisibilityProvider {
private val showSilentNotifsUri =
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
@@ -201,7 +203,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
// device isn't public, no need to check public-related settings, so allow
!lockscreenUserManager.isLockscreenPublicMode(user) -> false
// entry is meant to be secret on the lockscreen, disallow
- entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true
+ isRankingVisibilitySecret(entry) -> true
// disallow if user disallows notifications in public
else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)
}
@@ -215,6 +217,17 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
}
}
+ private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean {
+ return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
+ // info, and NotificationLockscreenUserManagerImpl is already listening for updates
+ // to those
+ entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
+ } else {
+ entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
println("isLockedOrLocking=$isLockedOrLocking")
withIncreasedIndent {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9340b85a743d..11c65e542bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1901,16 +1901,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return traceTag;
}
- if (isSummaryWithChildren()) {
- return traceTag + "(summary)";
- }
- Class<? extends Notification.Style> style =
- getEntry().getSbn().getNotification().getNotificationStyle();
- if (style == null) {
- return traceTag + "(nostyle)";
- } else {
- return traceTag + "(" + style.getSimpleName() + ")";
- }
+ return traceTag + "(" + getEntry().getNotificationStyle() + ")";
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a27a305428c4..60e75ff9e6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1363,12 +1363,12 @@ public class NotificationContentView extends FrameLayout implements Notification
result.mController.setPendingIntent(existingPendingIntent);
}
if (result.mController.updatePendingIntentFromActions(actions)) {
- if (!result.mView.isActive()) {
- result.mView.focus();
+ if (!result.mController.isActive()) {
+ result.mController.focus();
}
} else {
- if (result.mView.isActive()) {
- result.mView.close();
+ if (result.mController.isActive()) {
+ result.mController.close();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 79f8f22fd753..dba93d9718cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2735,7 +2735,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* @param listener callback for notification removed
*/
public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mOnNotificationRemovedListener = listener;
}
@@ -4982,12 +4982,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Nullable
public ExpandableView getShelf() {
- if (!mShelfRefactor.expectEnabled()) return null;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null;
return mShelf;
}
public void setShelf(NotificationShelf shelf) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
@@ -5001,7 +5001,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
public void setShelfController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 50207806ecaa..6a70815f82f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1431,7 +1431,7 @@ public class NotificationStackScrollLayoutController {
}
public void setShelfController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mView.setShelfController(notificationShelfController);
}
@@ -1644,12 +1644,12 @@ public class NotificationStackScrollLayoutController {
}
public void setShelf(NotificationShelf shelf) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mView.setShelf(shelf);
}
public int getShelfHeight() {
- if (!mShelfRefactor.expectEnabled()) {
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) {
return 0;
}
ExpandableView shelf = mView.getShelf();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 1576aa2a4a0c..6f992ac815cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -47,6 +47,11 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();
private int mIconTint = DEFAULT_ICON_TINT;
+ private int mContrastTint = DEFAULT_INVERSE_ICON_TINT;
+
+ private int mDarkModeContrastColor = DEFAULT_ICON_TINT;
+ private int mLightModeContrastColor = DEFAULT_INVERSE_ICON_TINT;
+
private float mDarkIntensity;
private int mDarkModeIconColorSingleTone;
private int mLightModeIconColorSingleTone;
@@ -83,6 +88,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
public void addDarkReceiver(DarkReceiver receiver) {
mReceivers.put(receiver, receiver);
receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
public void addDarkReceiver(ImageView imageView) {
@@ -90,6 +96,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));
mReceivers.put(imageView, receiver);
receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
public void removeDarkReceiver(DarkReceiver object) {
@@ -102,6 +109,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
public void applyDark(DarkReceiver object) {
mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ mReceivers.get(object).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
/**
@@ -125,8 +133,13 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
@Override
public void applyDarkIntensity(float darkIntensity) {
mDarkIntensity = darkIntensity;
- mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
+
+ mIconTint = (int) evaluator.evaluate(darkIntensity,
mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone);
+ mContrastTint = (int) evaluator
+ .evaluate(darkIntensity, mLightModeContrastColor, mDarkModeContrastColor);
+
applyIconTint();
}
@@ -139,6 +152,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));
for (int i = 0; i < mReceivers.size(); i++) {
mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
+ mReceivers.valueAt(i).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);
}
}
@@ -146,6 +160,16 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
public void dump(PrintWriter pw, String[] args) {
pw.println("DarkIconDispatcher: ");
pw.println(" mIconTint: 0x" + Integer.toHexString(mIconTint));
+ pw.println(" mContrastTint: 0x" + Integer.toHexString(mContrastTint));
+
+ pw.println(" mDarkModeIconColorSingleTone: 0x"
+ + Integer.toHexString(mDarkModeIconColorSingleTone));
+ pw.println(" mLightModeIconColorSingleTone: 0x"
+ + Integer.toHexString(mLightModeIconColorSingleTone));
+
+ pw.println(" mDarkModeContrastColor: 0x" + Integer.toHexString(mDarkModeContrastColor));
+ pw.println(" mLightModeContrastColor: 0x" + Integer.toHexString(mLightModeContrastColor));
+
pw.println(" mDarkIntensity: " + mDarkIntensity + "f");
pw.println(" mTintAreas: " + mTintAreas);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index de9854afdba1..5deb08a75dff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -28,10 +28,10 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
@@ -54,6 +54,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
private ModernStatusBarWifiView mModernWifiView;
private boolean mDemoMode;
private int mColor;
+ private int mContrastColor;
private final MobileIconsViewModel mMobileIconsViewModel;
private final StatusBarLocation mLocation;
@@ -68,6 +69,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
mStatusIcons = statusIcons;
mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+ mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT;
mMobileIconsViewModel = mobileIconsViewModel;
mLocation = location;
@@ -89,15 +91,17 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
((ViewGroup) getParent()).removeView(this);
}
- public void setColor(int color) {
+ /** Set the tint colors */
+ public void setColor(int color, int contrastColor) {
mColor = color;
+ mContrastColor = contrastColor;
updateColors();
}
private void updateColors() {
for (int i = 0; i < getChildCount(); i++) {
StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i);
- child.setStaticDrawableColor(mColor);
+ child.setStaticDrawableColor(mColor, mContrastColor);
child.setDecorColor(mColor);
}
}
@@ -223,7 +227,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false);
v.setTag(slot);
v.set(icon);
- v.setStaticDrawableColor(mColor);
+ v.setStaticDrawableColor(mColor, mContrastColor);
v.setDecorColor(mColor);
addView(v, 0, createLayoutParams());
}
@@ -269,7 +273,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
}
mModernWifiView = view;
- mModernWifiView.setStaticDrawableColor(mColor);
+ mModernWifiView.setStaticDrawableColor(mColor, mContrastColor);
addView(view, viewIndex, createLayoutParams());
}
@@ -305,14 +309,20 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
}
@Override
- public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
- setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
+ public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {
+ setColor(tint, contrastTint);
if (mModernWifiView != null) {
- mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+ mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint);
}
+
for (ModernStatusBarMobileView view : mModernMobileViews) {
- view.onDarkChanged(areas, darkIntensity, tint);
+ view.onDarkChangedWithContrast(areas, tint, contrastTint);
}
}
+
+ @Override
+ public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
+ // not needed
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index fb5a530e3875..0a03af7d9387 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -26,14 +26,20 @@ import android.util.MathUtils;
import com.android.app.animation.Interpolators;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.Logger;
+import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
+import javax.inject.Inject;
+
/**
* Utility class to calculate the clock position and top padding of notifications on Keyguard.
*/
public class KeyguardClockPositionAlgorithm {
+ private static final String TAG = "KeyguardClockPositionAlgorithm";
/**
* Margin between the bottom of the status view and the notification shade.
@@ -147,6 +153,13 @@ public class KeyguardClockPositionAlgorithm {
*/
private boolean mIsClockTopAligned;
+ private Logger mLogger;
+
+ @Inject
+ public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) {
+ mLogger = new Logger(logBuffer, TAG);
+ }
+
/**
* Refreshes the dimension values.
*/
@@ -306,6 +319,20 @@ public class KeyguardClockPositionAlgorithm {
+ fullyDarkBurnInOffset
+ shift;
mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
+ final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount;
+ final String outputs = "clockY: " + clockY
+ + " burnInPreventionOffsetY: " + burnInPreventionOffsetY
+ + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
+ + " shift: " + shift
+ + " mOverStretchAmount: " + mOverStretchAmount
+ + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
+ mLogger.i(msg -> {
+ return msg.getStr1() + " -> " + msg.getStr2();
+ }, msg -> {
+ msg.setStr1(inputs);
+ msg.setStr2(outputs);
+ return kotlin.Unit.INSTANCE;
+ });
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 58126ae41a1d..8a64a509a0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -436,10 +436,14 @@ public class KeyguardStatusBarView extends RelativeLayout {
private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
@ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
R.attr.wallpaperTextColor);
+ float luminance = Color.luminance(textColor);
@ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
- Color.luminance(textColor) < 0.5
+ luminance < 0.5
? com.android.settingslib.R.color.dark_mode_icon_color_single_tone
: com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+ @ColorInt int contrastColor = luminance < 0.5
+ ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT
+ : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;
float intensity = textColor == Color.WHITE ? 0 : 1;
mCarrierLabel.setTextColor(iconColor);
@@ -451,7 +455,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
}
if (iconManager != null) {
- iconManager.setTint(iconColor);
+ iconManager.setTint(iconColor, contrastColor);
}
mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 5553270ed0ae..f9856b0415e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -205,14 +205,13 @@ public class LegacyNotificationIconAreaControllerImpl implements
}
public void setupShelf(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mShelfIcons = notificationShelfController.getShelfIcons();
}
public void setShelfIcons(NotificationIconContainer icons) {
- if (mShelfRefactor.expectEnabled()) {
- mShelfIcons = icons;
- }
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+ mShelfIcons = icons;
}
public void onDensityOrFontScaleChanged(@NotNull Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index ffeb1a8cb8b6..9ae41951bb74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -31,11 +31,11 @@ import android.widget.LinearLayout.LayoutParams;
import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusIconDisplayable;
@@ -248,7 +248,10 @@ public interface StatusBarIconController {
*
*/
class TintedIconManager extends IconManager {
+ // The main tint, used as the foreground in non layer drawables
private int mColor;
+ // To be used as the main tint in drawables that wish to have a layer
+ private int mForegroundColor;
public TintedIconManager(
ViewGroup group,
@@ -268,26 +271,41 @@ public interface StatusBarIconController {
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
- view.setStaticDrawableColor(mColor);
+ view.setStaticDrawableColor(mColor, mForegroundColor);
view.setDecorColor(mColor);
}
- public void setTint(int color) {
- mColor = color;
+ /**
+ * Most icons are a single layer, and tintColor will be used as the tint in those cases.
+ * For icons that have a background, foregroundColor becomes the contrasting tint used
+ * for the foreground.
+ *
+ * @param tintColor the main tint to use for the icons in the group
+ * @param foregroundColor used as the main tint for layer-ish drawables where tintColor is
+ * being used as the background
+ */
+ public void setTint(int tintColor, int foregroundColor) {
+ mColor = tintColor;
+ mForegroundColor = foregroundColor;
+
for (int i = 0; i < mGroup.getChildCount(); i++) {
View child = mGroup.getChildAt(i);
if (child instanceof StatusIconDisplayable) {
StatusIconDisplayable icon = (StatusIconDisplayable) child;
- icon.setStaticDrawableColor(mColor);
+ icon.setStaticDrawableColor(mColor, mForegroundColor);
icon.setDecorColor(mColor);
}
}
+
+ if (mDemoStatusIcons != null) {
+ mDemoStatusIcons.setColor(tintColor, foregroundColor);
+ }
}
@Override
protected DemoStatusIcons createDemoStatusIcons() {
DemoStatusIcons icons = super.createDemoStatusIcons();
- icons.setColor(mColor);
+ icons.setColor(mColor, mForegroundColor);
return icons;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
new file mode 100644
index 000000000000..85fd2afed9ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+
+/** A dialog shown as a bottom sheet. */
+open class SystemUIBottomSheetDialog(
+ context: Context,
+ theme: Int = R.style.Theme_SystemUI_Dialog,
+) : Dialog(context, theme) {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ setGravity(Gravity.BOTTOM)
+ val edgeToEdgeHorizontally =
+ context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ if (edgeToEdgeHorizontally) {
+ decorView.setPadding(0, 0, 0, 0)
+ setLayout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT
+ )
+
+ val lp = attributes
+ lp.fitInsetsSides = 0
+ lp.horizontalMargin = 0f
+ attributes = lp
+ }
+ }
+ setCanceledOnTouchOutside(true)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 2c15e27b8148..de37170b1f7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
/**
* When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -65,7 +67,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
private val interactionJankMonitor: InteractionJankMonitor,
private val powerManager: PowerManager,
- private val handler: Handler = Handler()
+ private val handler: Handler = Handler(),
+ private val featureFlags: FeatureFlags,
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
private lateinit var shadeViewController: ShadeViewController
@@ -285,7 +288,11 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// up, with unpredictable consequences.
if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
shouldAnimateInKeyguard) {
- aodUiAnimationPlaying = true
+ if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ // Tracking this state should no longer be relevant, as the isInteractive
+ // check covers it
+ aodUiAnimationPlaying = true
+ }
// Show AOD. That'll cause the KeyguardVisibilityHelper to call
// #animateInKeyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index aacdc6323179..3522b9a13989 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -190,6 +190,24 @@ constructor(
fun logOnSimStateChanged() {
buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")
}
+
+ fun logPrioritizedNetworkAvailable(netId: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = netId },
+ { "Found prioritized network (nedId=$int1)" },
+ )
+ }
+
+ fun logPrioritizedNetworkLost(netId: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = netId },
+ { "Lost prioritized network (nedId=$int1)" },
+ )
+ }
}
private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index a89b1b2db6b3..679426db99c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -131,6 +131,12 @@ interface MobileConnectionRepository {
*/
val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+ /**
+ * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
+ * network slice
+ */
+ val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
+
companion object {
/** The default number of levels to use for [numberOfLevels]. */
const val DEFAULT_NUM_LEVELS = 4
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index c576b822da15..caa9d1a8f0c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -191,6 +191,8 @@ class DemoMobileConnectionRepository(
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
/**
* Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
* from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
@@ -225,6 +227,7 @@ class DemoMobileConnectionRepository(
_resolvedNetworkType.value = resolvedNetworkType
isAllowedDuringAirplaneMode.value = false
+ hasPrioritizedNetworkCapabilities.value = event.slice
}
fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
@@ -250,6 +253,7 @@ class DemoMobileConnectionRepository(
_isGsm.value = false
_carrierNetworkChangeActive.value = false
isAllowedDuringAirplaneMode.value = true
+ hasPrioritizedNetworkCapabilities.value = false
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index d4ddb856eecf..4cd877eb1a14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -76,6 +76,7 @@ constructor(
val carrierNetworkChange = getString("carriernetworkchange") == "show"
val roaming = getString("roam") == "show"
val name = getString("networkname") ?: "demo mode"
+ val slice = getString("slice").toBoolean()
return Mobile(
level = level,
@@ -87,6 +88,7 @@ constructor(
carrierNetworkChange = carrierNetworkChange,
roaming = roaming,
name = name,
+ slice = slice,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 8b03f71a2729..0aa95f8821cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -36,6 +36,7 @@ sealed interface FakeNetworkEventModel {
val carrierNetworkChange: Boolean,
val roaming: Boolean,
val name: String,
+ val slice: Boolean = false,
) : FakeNetworkEventModel
data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 28be3be28928..27edd1ec0caa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -174,6 +174,13 @@ class CarrierMergedConnectionRepository(
*/
override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
+ /**
+ * It's not currently considered possible that a carrier merged network can have these
+ * prioritized capabilities. If we need to track them, we can add the same check as is in
+ * [MobileConnectionRepositoryImpl].
+ */
+ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
+
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index ee11c06ef3f5..6b6192186e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -309,6 +309,15 @@ class FullMobileConnectionRepository(
activeRepo.value.isAllowedDuringAirplaneMode.value,
)
+ override val hasPrioritizedNetworkCapabilities =
+ activeRepo
+ .flatMapLatest { it.hasPrioritizedNetworkCapabilities }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.hasPrioritizedNetworkCapabilities.value,
+ )
+
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index dc50990d002a..760dd7eba3cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -21,6 +21,11 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
@@ -91,6 +96,7 @@ class MobileConnectionRepositoryImpl(
subscriptionModel: StateFlow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
+ connectivityManager: ConnectivityManager,
private val telephonyManager: TelephonyManager,
systemUiCarrierConfig: SystemUiCarrierConfig,
broadcastDispatcher: BroadcastDispatcher,
@@ -374,11 +380,50 @@ class MobileConnectionRepositoryImpl(
/** Typical mobile connections aren't available during airplane mode. */
override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
+ /**
+ * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
+ * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
+ * these capabilities that are used here must also be represented in the
+ * self_certified_network_capabilities.xml config file
+ */
+ @SuppressLint("WrongConstant")
+ private val networkSliceRequest =
+ NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .setSubscriptionIds(setOf(subId))
+ .build()
+
+ @SuppressLint("MissingPermission")
+ override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ // Our network callback listens only for this.subId && net_cap_prioritize_latency
+ // therefore our state is a simple mapping of whether or not that network exists
+ val callback =
+ object : NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ logger.logPrioritizedNetworkAvailable(network.netId)
+ trySend(true)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logPrioritizedNetworkLost(network.netId)
+ trySend(false)
+ }
+ }
+
+ connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .flowOn(bgDispatcher)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
class Factory
@Inject
constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val connectivityManager: ConnectivityManager,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
private val carrierConfigRepository: CarrierConfigRepository,
@@ -399,6 +444,7 @@ class MobileConnectionRepositoryImpl(
subscriptionModel,
defaultNetworkName,
networkNameSeparator,
+ connectivityManager,
telephonyManager.createForSubscriptionId(subId),
carrierConfigRepository.getOrCreateConfigForSubId(subId),
broadcastDispatcher,
@@ -421,11 +467,17 @@ private fun Intent.carrierId(): Int =
*/
sealed interface CallbackEvent {
data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+
data class OnDataActivity(val direction: Int) : CallbackEvent
+
data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+
data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+
data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
+
data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
+
data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4bf297cd088c..fe49c0791370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -76,6 +76,9 @@ interface MobileIconInteractor {
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+ /** Whether or not to show the slice attribution */
+ val showSliceAttribution: StateFlow<Boolean>
+
/**
* Provider name for this network connection. The name can be one of 3 values:
* 1. The default network name, if one is configured
@@ -238,6 +241,9 @@ class MobileIconInteractorImpl(
DefaultIcon(defaultMobileIconGroup.value),
)
+ override val showSliceAttribution: StateFlow<Boolean> =
+ connectionRepository.hasPrioritizedNetworkCapabilities
+
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index f0470ca0a1ad..b93e44378280 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
+import android.annotation.ColorInt
import android.content.res.ColorStateList
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.Space
import androidx.core.view.isVisible
@@ -28,10 +30,11 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
-import com.android.systemui.res.R
import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
@@ -43,6 +46,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
+private data class Colors(
+ @ColorInt val tint: Int,
+ @ColorInt val contrast: Int,
+)
+
object MobileIconBinder {
/** Binds the view to the view-model, continuing to update the former based on the latter */
@JvmStatic
@@ -57,6 +65,7 @@ object MobileIconBinder {
val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
+ val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
@@ -70,7 +79,13 @@ object MobileIconBinder {
@StatusBarIconView.VisibleState
val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState)
- val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ val iconTint: MutableStateFlow<Colors> =
+ MutableStateFlow(
+ Colors(
+ tint = DarkIconDispatcher.DEFAULT_ICON_TINT,
+ contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT
+ )
+ )
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
var isCollecting = false
@@ -139,7 +154,26 @@ object MobileIconBinder {
dataTypeId,
)
dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
- networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ networkTypeContainer.visibility =
+ if (dataTypeId != null) VISIBLE else GONE
+ }
+ }
+
+ // Set the network type background
+ launch {
+ viewModel.networkTypeBackground.collect { background ->
+ networkTypeContainer.setBackgroundResource(background?.res ?: 0)
+
+ // Tint will invert when this bit changes
+ if (background?.res != null) {
+ networkTypeContainer.backgroundTintList =
+ ColorStateList.valueOf(iconTint.value.tint)
+ networkTypeView.imageTintList =
+ ColorStateList.valueOf(iconTint.value.contrast)
+ } else {
+ networkTypeView.imageTintList =
+ ColorStateList.valueOf(iconTint.value.tint)
+ }
}
}
@@ -164,14 +198,24 @@ object MobileIconBinder {
// Set the tint
launch {
- iconTint.collect { tint ->
- val tintList = ColorStateList.valueOf(tint)
- iconView.imageTintList = tintList
- networkTypeView.imageTintList = tintList
- roamingView.imageTintList = tintList
- activityIn.imageTintList = tintList
- activityOut.imageTintList = tintList
- dotView.setDecorColor(tint)
+ iconTint.collect { colors ->
+ val tint = ColorStateList.valueOf(colors.tint)
+ val contrast = ColorStateList.valueOf(colors.contrast)
+
+ iconView.imageTintList = tint
+
+ // If the bg is visible, tint it and use the contrast for the fg
+ if (viewModel.networkTypeBackground.value != null) {
+ networkTypeContainer.backgroundTintList = tint
+ networkTypeView.imageTintList = contrast
+ } else {
+ networkTypeView.imageTintList = tint
+ }
+
+ roamingView.imageTintList = tint
+ activityIn.imageTintList = tint
+ activityOut.imageTintList = tint
+ dotView.setDecorColor(colors.tint)
}
}
@@ -196,8 +240,8 @@ object MobileIconBinder {
visibilityState.value = state
}
- override fun onIconTintChanged(newTint: Int) {
- iconTint.value = newTint
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+ iconTint.value = Colors(tint = newTint, contrast = contrastTint)
}
override fun onDecorTintChanged(newTint: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index dfabeea96082..d88c9efdac76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -19,7 +19,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -47,6 +50,8 @@ interface MobileIconViewModelCommon {
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
val networkTypeIcon: Flow<Icon.Resource?>
+ /** The slice attribution. Drawn as a background layer */
+ val networkTypeBackground: StateFlow<Icon.Resource?>
val activityInVisible: Flow<Boolean>
val activityOutVisible: Flow<Boolean>
val activityContainerVisible: Flow<Boolean>
@@ -67,12 +72,12 @@ interface MobileIconViewModelCommon {
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
-class MobileIconViewModel
-constructor(
+class MobileIconViewModel(
override val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
airplaneModeInteractor: AirplaneModeInteractor,
constants: ConnectivityConstants,
+ flags: FeatureFlagsClassic,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
override val isVisible: StateFlow<Boolean> =
@@ -152,6 +157,20 @@ constructor(
.distinctUntilChanged()
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ override val networkTypeBackground =
+ if (!flags.isEnabled(NEW_NETWORK_SLICE_UI)) {
+ flowOf(null)
+ } else {
+ iconInteractor.showSliceAttribution.map {
+ if (it) {
+ Icon.Resource(R.drawable.mobile_network_type_background, null)
+ } else {
+ null
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
override val roaming: StateFlow<Boolean> =
iconInteractor.isRoaming
.logDiffsForTable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 0f55910d8779..be843ba8699f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
@@ -54,6 +55,7 @@ constructor(
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
+ private val flags: FeatureFlagsClassic,
@Application private val scope: CoroutineScope,
) {
@VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
@@ -113,6 +115,7 @@ constructor(
interactor.getMobileConnectionInteractorForSubId(subId),
airplaneModeInteractor,
constants,
+ flags,
scope,
)
.also { mobileIconSubIdCache[subId] = it }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index 81f8683411ca..790596ea5520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -32,8 +32,8 @@ interface ModernStatusBarViewBinding {
/** Notifies that the visibility state has changed. */
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
- /** Notifies that the icon tint has been updated. */
- fun onIconTintChanged(newTint: Int)
+ /** Notifies that the icon tint has been updated. Includes a contrast for layered drawables */
+ fun onIconTintChanged(newTint: Int, contrastTint: Int)
/** Notifies that the decor tint has been updated (used only for the dot). */
fun onDecorTintChanged(newTint: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index fe69d818dedb..3b87bed2e0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -20,8 +20,8 @@ import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.Gravity
-import com.android.systemui.res.R
import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,13 +51,23 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :
override fun getSlot() = slot
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ // nop
+ }
+
+ override fun onDarkChangedWithContrast(areas: ArrayList<Rect>, tint: Int, contrastTint: Int) {
val newTint = DarkIconDispatcher.getTint(areas, this, tint)
- binding.onIconTintChanged(newTint)
+ val contrast = DarkIconDispatcher.getInverseTint(areas, this, contrastTint)
+
+ binding.onIconTintChanged(newTint, contrast)
binding.onDecorTintChanged(newTint)
}
override fun setStaticDrawableColor(color: Int) {
- binding.onIconTintChanged(color)
+ // nop
+ }
+
+ override fun setStaticDrawableColor(color: Int, foregroundColor: Int) {
+ binding.onIconTintChanged(color, foregroundColor)
}
override fun setDecorColor(color: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index a9ac51d7c472..60055279055c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -23,9 +23,9 @@ import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
@@ -165,7 +165,7 @@ object WifiViewBinder {
visibilityState.value = state
}
- override fun onIconTintChanged(newTint: Int) {
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) {
iconTint.value = newTint
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 62e238164514..b614b6d0547d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -38,6 +38,7 @@ import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.res.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.user.UserSwitchDialogController;
@@ -148,6 +149,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -160,7 +162,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
mStatusBarStateController = statusBarStateController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
- screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+ screenOffAnimationController, /* animateYPos= */ false,
+ featureFlags, /* logBuffer= */ null);
mUserSwitchDialogController = userSwitchDialogController;
mUiEventLogger = uiEventLogger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index bb074ac33ddb..dfe26865f978 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -38,10 +38,11 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -160,6 +161,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
+ FeatureFlags featureFlags,
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardUserSwitcherView);
@@ -174,7 +176,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS
mUserSwitcherController, this);
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
- screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+ screenOffAnimationController, /* animateYPos= */ false,
+ featureFlags, /* logBuffer= */ null);
mBackground = new KeyguardUserSwitcherScrim(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 53fed3d2438f..ceed81a182aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -305,7 +305,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
// Pass null to ensure all inputs are cleared for this entry b/227115380
- mController.removeRemoteInput(mEntry, null);
+ mController.removeRemoteInput(mEntry, null,
+ /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
}
}
}
@@ -426,7 +427,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@VisibleForTesting
void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
- mController.removeRemoteInput(mEntry, mToken);
+ mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus");
mEntry.remoteInputText = mEditText.getText();
// During removal, we get reattached and lose focus. Not hiding in that
@@ -536,7 +537,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
- mController.removeRemoteInput(mEntry, mToken);
+ mController.removeRemoteInput(mEntry, mToken,
+ /* reason= */"RemoteInputView#onDetachedFromWindow");
mController.removeSpinning(mEntry.getKey(), mToken);
}
@@ -655,7 +657,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mEditText.setText(mEntry.remoteInputText);
mEditText.setSelection(mEditText.length());
mEditText.requestFocus();
- mController.addRemoteInput(mEntry, mToken);
+ mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus");
setAttachment(mEntry.remoteInputAttachment);
updateSendButton();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index a50fd6f86e09..6c0d43394074 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -255,7 +255,8 @@ class RemoteInputViewControllerImpl @Inject constructor(
entry.lastRemoteInputSent = SystemClock.elapsedRealtime()
entry.mRemoteEditImeAnimatingAway = true
remoteInputController.addSpinning(entry.key, view.mToken)
- remoteInputController.removeRemoteInput(entry, view.mToken)
+ remoteInputController.removeRemoteInput(entry, view.mToken,
+ /* reason= */ "RemoteInputViewController#sendRemoteInput")
remoteInputController.remoteInputSent(entry)
entry.setHasSentReply()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6afa5256523c..4d8768f5e9e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,9 +25,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
@@ -47,8 +49,8 @@ import com.android.systemui.util.mockito.mock
import java.util.TimeZone
import java.util.concurrent.Executor
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -90,10 +92,10 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var smallClockEvents: ClockFaceEvents
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
- @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
private lateinit var repository: FakeKeyguardRepository
@Mock private lateinit var smallLogBuffer: LogBuffer
@Mock private lateinit var largeLogBuffer: LogBuffer
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: ClockEventController
@Before
@@ -125,17 +127,13 @@ class ClockEventControllerTest : SysuiTestCase() {
withDeps.featureFlags.apply {
set(Flags.REGION_SAMPLING, false)
- set(Flags.DOZING_MIGRATION_1, false)
+ set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
set(Flags.FACE_AUTH_REFACTOR, false)
}
underTest =
ClockEventController(
withDeps.keyguardInteractor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- featureFlags = withDeps.featureFlags,
- )
- .keyguardTransitionInteractor,
+ keyguardTransitionInteractor,
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
@@ -316,6 +314,68 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
+ fun listenForDozeAmountTransition_updatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep)
+
+ val job = underTest.listenForDozeAmountTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.4f
+ )
+ yield()
+
+ verify(animations, times(2)).doze(0.4f)
+
+ job.cancel()
+ }
+
+ @Test
+ fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToAodTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, times(2)).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
+ fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToAodTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, never()).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
fun unregisterListeners_validate() =
runBlocking(IMMEDIATE) {
underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 3b8e02f7455a..22c75d85d4a1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +33,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -60,6 +63,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
@Mock protected FeatureFlags mFeatureFlags;
@Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
+ @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakePowerRepository mFakePowerRepository;
@@ -90,6 +94,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
mFeatureFlags,
mInteractionJankMonitor,
deps.getKeyguardInteractor(),
+ mKeyguardTransitionInteractor,
mDumpManager,
PowerInteractorFactory.create(
mFakePowerRepository
@@ -105,8 +110,8 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
};
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+ when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
}
protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da72618fb60..5346db1aa067 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -87,6 +90,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
MockitoAnnotations.initMocks(this);
mContextWrapper = new ContextWrapper(mContext) {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index fd258e38a00f..3b2ea0f5ce86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -56,6 +59,7 @@ public class DismissAnimationControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
mock(SecureSettings.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 3a8bcd0ec2f0..76a3153e648a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
@@ -75,6 +78,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
@@ -96,6 +100,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
mLastIsMoveToTucked);
mEndListenerCaptor.getAllValues().clear();
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index 9b819479ec70..83bcd8d50407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
@@ -43,6 +46,7 @@ public class MenuEduTooltipViewTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 5764839d160d..e01f1b76df79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
@@ -72,6 +75,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Before
public void setUp() {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 98be49f9d493..a88ee108141b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -18,6 +18,9 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.view.View.OVER_SCROLL_NEVER;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -33,6 +36,7 @@ import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
@@ -79,6 +83,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
mock(SecureSettings.class));
@@ -213,5 +218,6 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@After
public void tearDown() {
mMotionEventHelper.recycleEvents();
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 31824ecaa432..41e5c209e344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
@@ -73,6 +76,7 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager wm = mContext.getSystemService(WindowManager.class);
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 5bb5e01675be..b0776c9a5b58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -22,7 +22,9 @@ import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +52,7 @@ import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -110,6 +113,7 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final Rect mDisplayBounds = new Rect();
mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
DISPLAY_WINDOW_HEIGHT);
@@ -145,6 +149,7 @@ public class MenuViewLayerTest extends SysuiTestCase {
UserHandle.USER_CURRENT);
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
mMenuViewLayer.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 5cd0fd0bb42d..ac2bfaf8eddf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -18,6 +18,9 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -67,6 +70,7 @@ public class MenuViewTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
deleted file mode 100644
index 712eef13421b..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.domain.interactor
-
-import android.hardware.biometrics.SensorLocationInternal
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.junit.MockitoJUnit
-
-@SmallTest
-@RunWith(JUnit4::class)
-class SideFpsOverlayInteractorTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private lateinit var testScope: TestScope
-
- private val fingerprintRepository = FakeFingerprintPropertyRepository()
-
- private lateinit var interactor: SideFpsOverlayInteractor
-
- @Before
- fun setup() {
- testScope = TestScope(StandardTestDispatcher())
- interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
- }
-
- @Test
- fun testOverlayOffsetUpdates() =
- testScope.runTest {
- fingerprintRepository.setProperties(
- sensorId = 1,
- strength = SensorStrength.STRONG,
- sensorType = FingerprintSensorType.REAR,
- sensorLocations =
- mapOf(
- "" to
- SensorLocationInternal(
- "" /* displayId */,
- 540 /* sensorLocationX */,
- 1636 /* sensorLocationY */,
- 130 /* sensorRadius */
- ),
- "display_id_1" to
- SensorLocationInternal(
- "display_id_1" /* displayId */,
- 100 /* sensorLocationX */,
- 300 /* sensorLocationY */,
- 20 /* sensorRadius */
- )
- )
- )
-
- val displayId by collectLastValue(interactor.displayId)
- val offsets by collectLastValue(interactor.overlayOffsets)
-
- // Assert offsets of empty displayId.
- assertThat(displayId).isEqualTo("")
- assertThat(offsets?.displayId).isEqualTo("")
- assertThat(offsets?.sensorLocationX).isEqualTo(540)
- assertThat(offsets?.sensorLocationY).isEqualTo(1636)
- assertThat(offsets?.sensorRadius).isEqualTo(130)
-
- // Offsets should be updated correctly.
- interactor.onDisplayChanged("display_id_1")
- assertThat(displayId).isEqualTo("display_id_1")
- assertThat(offsets?.displayId).isEqualTo("display_id_1")
- assertThat(offsets?.sensorLocationX).isEqualTo(100)
- assertThat(offsets?.sensorLocationY).isEqualTo(300)
- assertThat(offsets?.sensorRadius).isEqualTo(20)
-
- // Should return default offset when the displayId is invalid.
- interactor.onDisplayChanged("invalid_display_id")
- assertThat(displayId).isEqualTo("invalid_display_id")
- assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
- assertThat(offsets?.sensorLocationX)
- .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
- assertThat(offsets?.sensorLocationY)
- .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
- assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
new file mode 100644
index 000000000000..99501c426e0c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270
+import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.REST_TO_UNLOCK
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsSensorInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private lateinit var testScope: TestScope
+
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
+
+ private lateinit var underTest: SideFpsSensorInteractor
+
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var displayStateInteractor: DisplayStateInteractor
+
+ private val contextDisplayInfo = DisplayInfo()
+ private val displayChangeEvent = MutableStateFlow(0)
+ private val currentRotation = MutableStateFlow(ROTATION_0)
+
+ @Before
+ fun setup() {
+ testScope = TestScope(StandardTestDispatcher())
+ mContext = spy(mContext)
+
+ val displayManager = mock(DisplayManagerGlobal::class.java)
+ val resources = mContext.resources
+ whenever(mContext.display)
+ .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources))
+ whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent)
+ whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation)
+
+ contextDisplayInfo.uniqueId = "current-display"
+
+ underTest =
+ SideFpsSensorInteractor(
+ mContext,
+ fingerprintRepository,
+ windowManager,
+ displayStateInteractor,
+ FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) }
+ )
+ }
+
+ @Test
+ fun testSfpsSensorAvailable() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isAvailable)
+
+ setupFingerprint(FingerprintSensorType.POWER_BUTTON)
+ assertThat(isAvailable).isTrue()
+
+ setupFingerprint(FingerprintSensorType.HOME_BUTTON)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.REAR)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC)
+ assertThat(isAvailable).isFalse()
+
+ setupFingerprint(FingerprintSensorType.UNKNOWN)
+ assertThat(isAvailable).isFalse()
+ }
+
+ @Test
+ fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() =
+ testScope.runTest {
+ assertThat(collectLastValue(underTest.authenticationDuration)())
+ .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration))
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+ testScope.runTest {
+ /*
+ (0,0) (1000,0)
+ ------------------
+ | ^^^^^ | (1000, 200)
+ | status bar || <--- start of sensor at Rotation_0
+ | || <--- end of sensor
+ | | (1000, 300)
+ | |
+ ------------------ (1000, 800)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_0,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(1000)
+ assertThat(sensorLocation!!.top).isEqualTo(200)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+ testScope.runTest {
+ /*
+ (800,0) (800, 1000)
+ ---------------------
+ | | (600, 1000)
+ | < || <--- end of sensor at Rotation_270
+ | < status bar || <--- start of sensor
+ | < | (500, 1000)
+ | < |
+ (0,0) ---------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_270,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(500)
+ assertThat(sensorLocation!!.top).isEqualTo(1000)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+ testScope.runTest {
+ /*
+ (0,0)
+ ---------------------
+ | | (200, 0)
+ | > || <--- end of sensor at Rotation_270
+ | status bar > || <--- start of sensor
+ | > | (300, 0)
+ | > |
+ (800,1000) ---------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_90,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(200)
+ assertThat(sensorLocation!!.top).isEqualTo(0)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+ testScope.runTest {
+ /*
+
+ (1000,800) ---------------------
+ | | (0, 600)
+ | || <--- end of sensor at Rotation_270
+ | status bar || <--- start of sensor
+ | \/\/\/\/\/\/\/ | (0, 500)
+ | |
+ --------------------- (0,0)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_180,
+ sensorLocationY = 200,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(0)
+ assertThat(sensorLocation!!.top).isEqualTo(500)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() =
+ testScope.runTest {
+ /*
+ (0,0) (500,0) (600,0) (1000,0)
+ ____________===_________
+ | |
+ | ^^^^^ |
+ | status bar |
+ | |
+ ------------------------ (1000, 800)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_0,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(500)
+ assertThat(sensorLocation!!.top).isEqualTo(0)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() =
+ testScope.runTest {
+ /*
+ (0,1000) (0,500) (0,400) (0,0)
+ ____________===_________
+ | |
+ | > |
+ | status bar > |
+ | > |
+ (800, 1000) ------------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_90,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(0)
+ assertThat(sensorLocation!!.top).isEqualTo(400)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() =
+ testScope.runTest {
+ /*
+ (1000, 800) (500, 800) (400, 800) (0,800)
+ ____________===_________
+ | |
+ | |
+ | status bar |
+ | \/ \/ \/ \/ \/ \/ \/ |
+ ------------------------ (0,0)
+ */
+ setupFPLocationAndDisplaySize(
+ width = 1000,
+ height = 800,
+ rotation = ROTATION_180,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(400)
+ assertThat(sensorLocation!!.top).isEqualTo(800)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ @Test
+ fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() =
+ testScope.runTest {
+ /*
+ (800, 500) (800, 600)
+ (800, 0) ____________===_________ (800,1000)
+ | < |
+ | < |
+ | < status bar |
+ | < |
+ (0,0) ------------------------
+ */
+ setupFPLocationAndDisplaySize(
+ width = 800,
+ height = 1000,
+ rotation = ROTATION_270,
+ sensorLocationX = 500,
+ sensorWidth = 100,
+ )
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation!!.left).isEqualTo(800)
+ assertThat(sensorLocation!!.top).isEqualTo(500)
+ assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
+ assertThat(sensorLocation!!.width).isEqualTo(100)
+ }
+
+ private suspend fun TestScope.setupFPLocationAndDisplaySize(
+ width: Int,
+ height: Int,
+ sensorLocationX: Int = 0,
+ sensorLocationY: Int = 0,
+ rotation: DisplayRotation,
+ sensorWidth: Int
+ ) {
+ overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
+ setupDisplayDimensions(width, height)
+ currentRotation.value = rotation
+ setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+ }
+
+ private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
+ whenever(windowManager.maximumWindowMetrics)
+ .thenReturn(
+ WindowMetrics(
+ Rect(0, 0, displayWidth, displayHeight),
+ mock(WindowInsets::class.java)
+ )
+ )
+ }
+
+ private suspend fun TestScope.setupFingerprint(
+ fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
+ x: Int = 0,
+ y: Int = 0,
+ displayId: String = "display_id_1"
+ ) {
+ contextDisplayInfo.uniqueId = displayId
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = fingerprintSensorType,
+ sensorLocations =
+ mapOf(
+ "someOtherDisplayId" to
+ SensorLocationInternal(
+ "someOtherDisplayId",
+ x + 100,
+ y + 100,
+ 0,
+ ),
+ displayId to
+ SensorLocationInternal(
+ displayId,
+ x,
+ y,
+ 0,
+ )
+ )
+ )
+ // Emit a display change event, this happens whenever any display related change happens,
+ // rotation, active display changing etc, display switched off/on.
+ displayChangeEvent.emit(1)
+
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 37c70d8f25e0..2bd2bff80951 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -43,10 +43,10 @@ class FakeFeatureFlagsTest : SysuiTestCase() {
fun accessingUnspecifiedFlags_throwsException() {
val flags: FeatureFlags = FakeFeatureFlags()
try {
- assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+ assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse()
fail("Expected an exception when accessing an unspecified flag.")
} catch (ex: IllegalStateException) {
- assertThat(ex.message).contains("UNKNOWN(teamfood)")
+ assertThat(ex.message).contains("UNKNOWN(null_flag)")
}
try {
assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index c12a581fb2c1..f51745b9f17c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -65,6 +66,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<String>
private val serverFlagReader = ServerFlagReaderFake()
+ private val fakeGantryFlags = FakeFeatureFlagsImpl()
private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)
private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true)
@@ -72,7 +74,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+ fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false)
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
mFeatureFlagsClassicDebug =
@@ -84,6 +86,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
resources,
serverFlagReader,
flagMap,
+ fakeGantryFlags,
restarter
)
mFeatureFlagsClassicDebug.init()
@@ -121,8 +124,6 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
@Test
fun teamFoodFlag_False() {
- whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
- .thenReturn(false)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -133,8 +134,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
@Test
fun teamFoodFlag_True() {
- whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
- .thenReturn(true)
+ fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue()
@@ -149,8 +149,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
.thenReturn(true)
whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
.thenReturn(false)
- whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any()))
- .thenReturn(true)
+ fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()
assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
new file mode 100644
index 000000000000..bb6786ade21a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.platform.test.flag.junit.SetFlagsRule
+
+fun SetFlagsRule.setFlagDefault(flagName: String) {
+ if (getFlagDefault(flagName)) {
+ enableFlags(flagName)
+ } else {
+ disableFlags(flagName)
+ }
+}
+
+// NOTE: This code uses reflection to gain access to private members of aconfig generated
+// classes (in the same way SetFlagsRule does internally) because this is the only way to get
+// at the underlying information and read the current value of the flag.
+// If aconfig had flag constants with accessible default values, this would be unnecessary.
+private fun getFlagDefault(name: String): Boolean {
+ val flagPackage = name.substringBeforeLast(".")
+ val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl")
+ val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance()
+ val flagMethodName = name.substringAfterLast(".").snakeToCamelCase()
+ val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName)
+ return flagGetter.invoke(featureFlagsImpl) as Boolean
+}
+
+private fun String.snakeToCamelCase(): String {
+ val pattern = "_[a-z]".toRegex()
+ return replace(pattern) { it.value.last().uppercase() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index d8cdf29284ed..90fd6523ec12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -147,7 +147,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
emptyMap()
)
verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
- verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
+ verify(authController, times(2)).addCallback(authControllerCallback.capture())
}
@Test
@@ -314,18 +314,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
testScope.runTest {
createBiometricSettingsRepository()
+ val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
faceAuthIsEnabledByBiometricManager()
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
runCurrent()
- clearInvocations(authController)
- whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
- val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ enrollmentChange(FACE, PRIMARY_USER_ID, false)
assertThat(faceAuthAllowed()).isFalse()
- verify(authController).addCallback(authControllerCallback.capture())
+
enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(faceAuthAllowed()).isFalse()
@@ -375,25 +375,20 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
testScope.runTest {
createBiometricSettingsRepository()
+ val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
userRepository.setSelectedUserInfo(ANOTHER_USER)
doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
-
- runCurrent()
- clearInvocations(authController)
-
- val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- runCurrent()
-
- verify(authController).addCallback(authControllerCallback.capture())
-
+ onNonStrongAuthChanged(true, ANOTHER_USER_ID)
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
+ runCurrent()
- assertThat(faceAuthAllowed()).isTrue()
+ assertThat(faceAuthAllowed).isTrue()
}
@Test
@@ -637,6 +632,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
@@ -660,6 +656,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsNonStrongBiometric()
@@ -753,7 +750,9 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
- authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
+ authControllerCallback.allValues.forEach {
+ it.onEnrollmentsChanged(biometricType, userId, enabled)
+ }
}
private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 06eb0dd364b5..6ad2d731f375 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -43,7 +43,7 @@ import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -95,6 +95,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
FakeDeviceEntryFingerprintAuthRepository
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var powerInteractor: PowerInteractor
+ private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
@@ -123,6 +124,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
facePropertyRepository = FakeFacePropertyRepository()
fakeKeyguardRepository = FakeKeyguardRepository()
powerInteractor = PowerInteractorFactory.create().powerInteractor
+ fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
+
underTest =
SystemUIKeyguardFaceAuthInteractor(
mContext,
@@ -147,7 +150,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
mock(StatusBarStateController::class.java),
mock(KeyguardStateController::class.java),
bouncerRepository,
- mock(BiometricSettingsRepository::class.java),
+ fakeBiometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
),
@@ -160,6 +163,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
facePropertyRepository,
faceWakeUpTriggersConfig,
powerInteractor,
+ fakeBiometricSettingsRepository,
)
}
@@ -481,6 +485,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
fun faceUnlockIsDisabledWhenFpIsLockedOut() =
testScope.runTest {
underTest.start()
+ fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
new file mode 100644
index 000000000000..255f4df17244
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GoneToAodTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: GoneToAodTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
+ repository = FakeKeyguardTransitionRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest = GoneToAodTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun enterFromTopTranslationY() =
+ testScope.runTest {
+ val pixels = -100f
+ val enterFromTopTranslationY by
+ collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+
+ // The animation should only start > halfway through
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+
+ // At the end, the translation should be complete and set to zero
+ repository.sendTransitionStep(step(1f))
+ assertThat(enterFromTopTranslationY).isEqualTo(0f)
+ }
+
+ @Test
+ fun enterFromTopAnimationAlpha() =
+ testScope.runTest {
+ val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+
+ // The animation should only start > halfway through
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f))
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(1f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "GoneToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 71688db76a8b..4f545cb0e288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -26,12 +28,17 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.ClockController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -41,6 +48,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
@SmallTest
@@ -51,10 +59,20 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var configurationRepository: FakeConfigurationRepository
@Mock private lateinit var burnInInteractor: BurnInInteractor
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+ @Mock
+ private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
private val burnInFlow = MutableStateFlow(BurnInModel())
+ private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0)
+ private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+ private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+ private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+ private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE)
@Before
fun setUp() {
@@ -71,9 +89,30 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
keyguardInteractor = withDeps.keyguardInteractor
repository = withDeps.repository
+ configurationRepository = withDeps.configurationRepository
+
+ whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+ .thenReturn(emptyFlow<Float>())
+ whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+ .thenReturn(enterFromTopAnimationAlpha)
whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
- underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor)
+
+ whenever(keyguardTransitionInteractor.goneToAodTransition)
+ .thenReturn(goneToAodTransitionStep)
+ whenever(keyguardTransitionInteractor.dozeAmountTransition)
+ .thenReturn(dozeAmountTransitionStep)
+ whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState)
+
+ underTest =
+ KeyguardRootViewModel(
+ context,
+ keyguardInteractor,
+ burnInInteractor,
+ goneToAodTransitionViewModel,
+ aodToLockscreenTransitionViewModel,
+ keyguardTransitionInteractor,
+ )
underTest.clockControllerProvider = Provider { clockController }
}
@@ -118,7 +157,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
val scale by collectLastValue(underTest.scale)
// Set to not dozing (on lockscreen)
- repository.setDozeAmount(0f)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 0f))
// Trigger a change to the burn-in model
burnInFlow.value =
@@ -141,8 +180,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
val scale by collectLastValue(underTest.scale)
// Set to dozing (on AOD)
- repository.setDozeAmount(1f)
-
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
burnInFlow.value =
BurnInModel(
@@ -150,10 +188,15 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
translationY = 30,
scale = 0.5f,
)
-
assertThat(translationX).isEqualTo(20)
assertThat(translationY).isEqualTo(30)
assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+ // Set to the beginning of GONE->AOD transition
+ goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
}
@Test
@@ -166,7 +209,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
val scale by collectLastValue(underTest.scale)
// Set to dozing (on AOD)
- repository.setDozeAmount(1f)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
burnInFlow.value =
@@ -180,4 +223,28 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
assertThat(translationY).isEqualTo(0)
assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
}
+
+ @Test
+ fun burnInLayerVisibility() =
+ testScope.runTest {
+ val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+ startedKeyguardState.value = KeyguardState.OCCLUDED
+ assertThat(burnInLayerVisibility).isNull()
+
+ startedKeyguardState.value = KeyguardState.AOD
+ assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun burnInLayerAlpha() =
+ testScope.runTest {
+ val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
+
+ enterFromTopAnimationAlpha.value = 0.2f
+ assertThat(burnInLayerAlpha).isEqualTo(0.2f)
+
+ enterFromTopAnimationAlpha.value = 1f
+ assertThat(burnInLayerAlpha).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index e537131bad01..4c5a2144941a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -54,11 +54,7 @@ import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
-import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -111,7 +107,8 @@ public class CastTileTest extends SysuiTestCase {
private WifiInteractor mWifiInteractor;
private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
- private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+ private final FakeConnectivityRepository mConnectivityRepository =
+ new FakeConnectivityRepository();
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private final TestScope mTestScope = TestScopeProvider.getTestScope();
@@ -124,12 +121,6 @@ public class CastTileTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
when(mHost.getContext()).thenReturn(mContext);
-
- mWifiInteractor = new WifiInteractorImpl(
- new FakeConnectivityRepository(),
- mWifiRepository,
- mTestScope
- );
}
@After
@@ -204,25 +195,41 @@ public class CastTileTest extends SysuiTestCase {
// SIGNAL_CALLBACK_DEPRECATION flag set to true
@Test
- public void stateUnavailable_wifiDisabled_newPipeline() {
+ public void stateUnavailable_noDefaultNetworks_newPipeline() {
createAndStartTileNewImpl();
- mWifiRepository.setIsWifiEnabled(false);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
}
@Test
- public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+ public void stateUnavailable_mobileConnected_newPipeline() {
createAndStartTileNewImpl();
- mWifiRepository.setIsWifiEnabled(true);
- mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+ mConnectivityRepository.setMobileConnected(true);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
}
@Test
+ public void stateInactive_wifiConnected_newPipeline() {
+ createAndStartTileNewImpl();
+ mConnectivityRepository.setWifiConnected(true);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateInactive_ethernetConnected_newPipeline() {
+ createAndStartTileNewImpl();
+ mConnectivityRepository.setEthernetConnected(true);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
public void stateActive_wifiConnectedAndCasting_newPipeline() {
createAndStartTileNewImpl();
CastController.CastDevice device = new CastController.CastDevice();
@@ -231,40 +238,27 @@ public class CastTileTest extends SysuiTestCase {
devices.add(device);
when(mController.getCastDevices()).thenReturn(devices);
- mWifiRepository.setWifiNetwork(
- new WifiNetworkModel.Active(
- 1 /* networkId */,
- true /* isValidated */,
- 3 /* level */,
- "test" /* ssid */,
- WifiNetworkModel.HotspotDeviceType.NONE,
- false /* isPasspointAccessPoint */,
- false /* isOnlineSignUpforPasspointAccessPoint */,
- null /* passpointProviderFriendlyName */
- ));
+ mConnectivityRepository.setWifiConnected(true);
+
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
}
@Test
- public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+ public void stateActive_ethernetConnectedAndCasting_newPipeline() {
createAndStartTileNewImpl();
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ mConnectivityRepository.setEthernetConnected(true);
- mWifiRepository.setWifiNetwork(
- new WifiNetworkModel.Active(
- 1 /* networkId */,
- true /* isValidated */,
- 3 /* level */,
- "test" /* ssid */,
- WifiNetworkModel.HotspotDeviceType.NONE,
- false /* isPasspointAccessPoint */,
- false /* isOnlineSignUpforPasspointAccessPoint */,
- null /* passpointProviderFriendlyName */
- ));
mTestableLooper.processAllMessages();
- assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
}
// -------------------------------------------------
@@ -512,7 +506,7 @@ public class CastTileTest extends SysuiTestCase {
mNetworkController,
mHotspotController,
mDialogLaunchAnimator,
- mWifiInteractor,
+ mConnectivityRepository,
mJavaAdapter,
mFeatureFlags
);
@@ -555,7 +549,7 @@ public class CastTileTest extends SysuiTestCase {
mNetworkController,
mHotspotController,
mDialogLaunchAnimator,
- mWifiInteractor,
+ mConnectivityRepository,
mJavaAdapter,
mFeatureFlags
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 58e36be2b9fd..10c7c439e5f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -21,6 +21,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -49,6 +51,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val testScope = utils.testScope
private val sceneInteractor = utils.sceneInteractor()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -61,6 +64,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 61dd69a8126b..88a5c17f4994 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -25,6 +25,8 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.model.SysUiState
@@ -137,6 +139,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -149,6 +152,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 3ae1f35b8134..da4dc850fcae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -111,6 +112,15 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
}
@Test
+ fun showDialog_singleAppIsDefault() {
+ dialog.show()
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+ val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app)
+ assertEquals(spinner.adapter.getItem(0), singleApp)
+ }
+
+ @Test
fun showDialog_cancelClicked_dialogIsDismissed() {
dialog.show()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 0d694ee80def..7e41745936f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@ import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -47,7 +46,6 @@ class RequestProcessorTest {
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val policy = FakeScreenshotPolicy()
- private val flags = FakeFeatureFlags()
/** Tests the Java-compatible function wrapper, ensures callback is invoked. */
@Test
@@ -58,7 +56,7 @@ class RequestProcessorTest {
.setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
.build()
)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
var result: ScreenshotData? = null
var callbackCount = 0
@@ -86,7 +84,7 @@ class RequestProcessorTest {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -111,7 +109,7 @@ class RequestProcessorTest {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -138,7 +136,7 @@ class RequestProcessorTest {
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
Assert.assertThrows(IllegalStateException::class.java) {
runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -148,7 +146,7 @@ class RequestProcessorTest {
@Test
fun testProvidedImageScreenshot() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
policy.setManagedProfile(USER_ID, false)
@@ -173,7 +171,7 @@ class RequestProcessorTest {
@Test
fun testProvidedImageScreenshot_managedProfile() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
// Indicate that the screenshot belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index a105c15b630a..3dc90374206a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -43,8 +44,11 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
private val controller0 = mock<ScreenshotController>()
private val controller1 = mock<ScreenshotController>()
+ private val notificationsController0 = mock<ScreenshotNotificationsController>()
+ private val notificationsController1 = mock<ScreenshotNotificationsController>()
private val controllerFactory = mock<ScreenshotController.Factory>()
private val callback = mock<TakeScreenshotService.RequestCallback>()
+ private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
private val fakeDisplayRepository = FakeDisplayRepository()
private val requestProcessor = FakeRequestProcessor()
@@ -59,12 +63,15 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
testScope,
requestProcessor,
eventLogger,
+ notificationControllerFactory
)
@Before
fun setUp() {
- whenever(controllerFactory.create(eq(0))).thenReturn(controller0)
- whenever(controllerFactory.create(eq(1))).thenReturn(controller1)
+ whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)
+ whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1)
+ whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
+ whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
}
@Test
@@ -74,8 +81,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
val onSaved = { _: Uri -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
- verify(controllerFactory).create(eq(0))
- verify(controllerFactory).create(eq(1))
+ verify(controllerFactory).create(eq(0), any())
+ verify(controllerFactory).create(eq(1), any())
val capturer = ArgumentCaptor<ScreenshotData>()
@@ -107,8 +114,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
callback
)
- verify(controllerFactory).create(eq(0))
- verify(controllerFactory, never()).create(eq(1))
+ verify(controllerFactory).create(eq(0), any())
+ verify(controllerFactory, never()).create(eq(1), any())
val capturer = ArgumentCaptor<ScreenshotData>()
@@ -139,7 +146,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
@Test
fun executeScreenshots_allowedTypes_allCaptured() =
testScope.runTest {
- whenever(controllerFactory.create(any())).thenReturn(controller0)
+ whenever(controllerFactory.create(any(), any())).thenReturn(controller0)
setDisplays(
display(TYPE_INTERNAL, id = 0),
@@ -310,6 +317,123 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
screenshotExecutor.onDestroy()
}
+ @Test
+ fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessor_logsUiError() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_reportsError() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ verify(notificationsController1).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
private suspend fun TestScope.setDisplays(vararg displays: Display) {
fakeDisplayRepository.emit(displays.toSet())
runCurrent()
@@ -328,8 +452,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
private class FakeRequestProcessor : ScreenshotRequestProcessor {
var processed: ScreenshotData? = null
var toReturn: ScreenshotData? = null
-
+ var shouldThrowException = false
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+ if (shouldThrowException) throw RequestProcessorException("")
processed = screenshot
return toReturn ?: screenshot
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 6205d90197a2..f3809aad4de3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -65,6 +65,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
private val requestProcessor = mock<RequestProcessor>()
private val devicePolicyManager = mock<DevicePolicyManager>()
private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+ private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
private val notificationsController = mock<ScreenshotNotificationsController>()
private val callback = mock<RequestCallback>()
@@ -86,7 +87,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
)
.thenReturn(false)
whenever(userManager.isUserUnlocked).thenReturn(true)
- whenever(controllerFactory.create(any())).thenReturn(controller)
+ whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+ whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
@@ -323,7 +325,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
userManager,
devicePolicyManager,
eventLogger,
- notificationsController,
+ notificationsControllerFactory,
mContext,
Runnable::run,
flags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 8d8c70e26ab2..6223e250d603 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -157,6 +157,7 @@ import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -328,6 +329,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock private CastController mCastController;
@Mock private KeyguardRootView mKeyguardRootView;
@Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
+ @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -388,6 +390,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mFeatureFlags,
mInteractionJankMonitor,
mKeyguardInteractor,
+ mKeyguardTransitionInteractor,
mDumpManager,
mPowerInteractor));
@@ -667,7 +670,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mKeyguardViewConfigurator,
mKeyguardFaceAuthInteractor,
new ResourcesSplitShadeStateController(),
- mPowerInteractor);
+ mPowerInteractor,
+ mKeyguardClockPositionAlgorithm);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 607cdab12f56..df38f936f4f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -4,6 +4,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -31,6 +33,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
private val sceneInteractor = utils.sceneInteractor()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -43,6 +46,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index c4237827c3ea..3064f4b2b355 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -21,6 +21,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -55,6 +57,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -67,6 +70,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
FakeConnectivityRepository(),
),
constants = mock(),
+ flags,
scope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
new file mode 100644
index 000000000000..3a9c24a7109c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
+import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.settings.FakeSettings;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
+ @Mock
+ private NotificationPresenter mPresenter;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UserTracker mUserTracker;
+
+ // Dependency mocks:
+ @Mock
+ private NotificationVisibilityProvider mVisibilityProvider;
+ @Mock
+ private CommonNotifCollection mNotifCollection;
+ @Mock
+ private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private NotificationClickNotifier mClickNotifier;
+ @Mock
+ private OverviewProxyService mOverviewProxyService;
+ @Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
+ private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+
+ private UserInfo mCurrentUser;
+ private UserInfo mSecondaryUser;
+ private UserInfo mWorkUser;
+ private FakeSettings mSettings;
+ private TestNotificationLockscreenUserManager mLockscreenUserManager;
+ private NotificationEntry mCurrentUserNotif;
+ private NotificationEntry mSecondaryUserNotif;
+ private NotificationEntry mWorkProfileNotif;
+ private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+
+ int currentUserId = ActivityManager.getCurrentUser();
+ when(mUserTracker.getUserId()).thenReturn(currentUserId);
+ mSettings = new FakeSettings();
+ mSettings.setUserId(ActivityManager.getCurrentUser());
+ mCurrentUser = new UserInfo(currentUserId, "", 0);
+ mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
+ mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED);
+
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
+ when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
+ mCurrentUser, mWorkUser));
+ when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
+ mSecondaryUser));
+ mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
+ Handler.createAsync(Looper.myLooper()));
+
+ Notification notifWithPrivateVisibility = new Notification();
+ notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+ mCurrentUserNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mCurrentUser.id))
+ .build();
+ mSecondaryUserNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mSecondaryUser.id))
+ .build();
+ mWorkProfileNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mWorkUser.id))
+ .build();
+
+ mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+ mLockscreenUserManager.setUpWithPresenter(mPresenter);
+ }
+
+ private void changeSetting(String setting) {
+ final Collection<Uri> lockScreenUris = new ArrayList<>();
+ lockScreenUris.add(Settings.Secure.getUriFor(setting));
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+ lockScreenUris, 0);
+ }
+
+ @Test
+ public void testLockScreenShowNotificationsFalse() {
+ mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
+ }
+
+ @Test
+ public void testLockScreenShowNotificationsTrue() {
+ mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
+ }
+
+ @Test
+ public void testLockScreenAllowPrivateNotificationsTrue() {
+ mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testLockScreenAllowPrivateNotificationsFalse() {
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+ }
+
+ @Test
+ public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
+ }
+
+ @Test
+ public void testCurrentUserPrivateNotificationsNotRedacted() {
+ // GIVEN current user doesn't allow private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN current user's notification is redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
+ public void testCurrentUserPrivateNotificationsRedacted() {
+ // GIVEN current user allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN current user's notification isn't redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
+ public void testWorkPrivateNotificationsRedacted() {
+ // GIVEN work profile doesn't private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN work profile notification is redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+ }
+
+ @Test
+ public void testWorkPrivateNotificationsNotRedacted() {
+ // GIVEN work profile allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mWorkUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN work profile notification isn't redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+ assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
+ }
+
+ @Test
+ public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
+ // GIVEN work profile allows private notifications to show but the other users don't
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mWorkUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN the work profile notification doesn't need to be redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+ // THEN the current user and secondary user notifications do need to be redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testWorkProfileRedacted_otherUsersNotRedacted() {
+ // GIVEN work profile doesn't allow private notifications to show but the other users do
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mWorkUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN the work profile notification needs to be redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
+
+ // THEN the current user and secondary user notifications don't need to be redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testSecondaryUserNotRedacted_currentUserRedacted() {
+ // GIVEN secondary profile allows private notifications to show but the current user
+ // doesn't allow private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // THEN the secondary profile notification still needs to be redacted because the current
+ // user's setting takes precedence
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testUserSwitchedCallsOnUserSwitching() {
+ mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
+ mContext);
+ verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
+ }
+
+ @Test
+ public void testIsLockscreenPublicMode() {
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+ mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
+ }
+
+ @Test
+ public void testUpdateIsPublicMode() {
+ when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+ NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+ mLockscreenUserManager.addNotificationStateChangedListener(listener);
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+ // first call explicitly sets user 0 to not public; notifies
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+
+ // Calling again with keyguard now showing makes user 0 public; notifies
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+ }
+
+ @Test
+ public void testDevicePolicyDoesNotAllowNotifications() {
+ // User allows them
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides notifs on lockscreen
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+ .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testDevicePolicy_noPrivateNotifications() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
+ public void testDevicePolicy_noPrivateNotifications_userAll() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
+ .setNotification(new Notification())
+ .setUser(UserHandle.ALL)
+ .build()));
+ }
+
+ @Test
+ public void testDevicePolicyPrivateNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ // DevicePolicy hides sensitive content
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+ assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
+ }
+
+ @Test
+ public void testHideNotifications_primary() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testHideNotifications_secondary() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testHideNotifications_secondary_userSwitch() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testShowNotifications_secondary_userSwitch() {
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ }
+
+ @Test
+ public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
+ // DevicePolicy allows notifications
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
+ .thenReturn(0);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
+ mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
+ mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
+ new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+
+ // KeyguardManager does not allow notifications
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+ // callback, so it's only updated when the setting is
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ @Test
+ public void testUserAllowsNotificationsInPublic_settingsChange() {
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+
+ // User disables
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
+ }
+
+ private class TestNotificationLockscreenUserManager
+ extends NotificationLockscreenUserManagerImpl {
+ public TestNotificationLockscreenUserManager(Context context) {
+ super(
+ context,
+ mBroadcastDispatcher,
+ mDevicePolicyManager,
+ mUserManager,
+ mUserTracker,
+ (() -> mVisibilityProvider),
+ (() -> mNotifCollection),
+ mClickNotifier,
+ (() -> mOverviewProxyService),
+ NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
+ mStatusBarStateController,
+ Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(Looper.myLooper()),
+ mBackgroundExecutor,
+ mDeviceProvisionedController,
+ mKeyguardStateController,
+ mSettings,
+ mock(DumpManager.class),
+ mock(LockPatternUtils.class),
+ mFakeFeatureFlags);
+ }
+
+ public BroadcastReceiver getBaseBroadcastReceiverForTest() {
+ return mBaseBroadcastReceiver;
+ }
+
+ public UserTracker.Callback getUserTrackerCallbackForTest() {
+ return mUserChangedCallback;
+ }
+
+ public ContentObserver getLockscreenSettingsObserverForTest() {
+ return mLockscreenSettingsObserver;
+ }
+
+ public ContentObserver getSettingsObserverForTest() {
+ return mSettingsObserver;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 19863ecaf723..a5f5fc7e36f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,17 +16,23 @@
package com.android.systemui.statusbar;
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
import static android.os.UserHandle.USER_ALL;
+import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -38,12 +44,15 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -59,6 +68,8 @@ import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
@@ -74,12 +85,16 @@ import com.android.systemui.util.settings.FakeSettings;
import com.google.android.collect.Lists;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -121,11 +136,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private NotificationEntry mCurrentUserNotif;
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
+ private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+
int currentUserId = ActivityManager.getCurrentUser();
when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
@@ -138,103 +157,144 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
mCurrentUser, mWorkUser));
+ when(mUserManager.getUsers()).thenReturn(Lists.newArrayList(
+ mCurrentUser, mWorkUser, mSecondaryUser));
when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
mSecondaryUser));
mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
Handler.createAsync(Looper.myLooper()));
Notification notifWithPrivateVisibility = new Notification();
- notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
+ notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
mCurrentUserNotif = new NotificationEntryBuilder()
.setNotification(notifWithPrivateVisibility)
.setUser(new UserHandle(mCurrentUser.id))
.build();
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+ mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);
mSecondaryUserNotif = new NotificationEntryBuilder()
.setNotification(notifWithPrivateVisibility)
.setUser(new UserHandle(mSecondaryUser.id))
.build();
+ mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(
+ mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);
mWorkProfileNotif = new NotificationEntryBuilder()
.setNotification(notifWithPrivateVisibility)
.setUser(new UserHandle(mWorkUser.id))
.build();
+ mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
mLockscreenUserManager.setUpWithPresenter(mPresenter);
}
+ private void changeSetting(String setting) {
+ final Collection<Uri> lockScreenUris = new ArrayList<>();
+ lockScreenUris.add(Settings.Secure.getUriFor(setting));
+ mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
+ lockScreenUris, 0);
+ }
+
@Test
public void testLockScreenShowNotificationsFalse() {
mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testLockScreenShowNotificationsTrue() {
mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testLockScreenAllowPrivateNotificationsTrue() {
- mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
}
@Test
public void testLockScreenAllowPrivateNotificationsFalse() {
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
}
@Test
public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
}
@Test
public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
}
@Test
- public void testCurrentUserPrivateNotificationsNotRedacted() {
+ public void testCurrentUserPrivateNotificationsRedacted() {
// GIVEN current user doesn't allow private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN current user's notification is redacted
assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
}
@Test
- public void testCurrentUserPrivateNotificationsRedacted() {
+ public void testCurrentUserPrivateNotificationsNotRedacted() {
// GIVEN current user allows private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN current user's notification isn't redacted
assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
}
@Test
+ public void testCurrentUserPrivateNotificationsRedactedChannel() {
+ // GIVEN current user allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // but doesn't allow it at the channel level
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
+ mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ // THEN the notification is redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
public void testWorkPrivateNotificationsRedacted() {
// GIVEN work profile doesn't private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN work profile notification is redacted
assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -244,9 +304,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testWorkPrivateNotificationsNotRedacted() {
// GIVEN work profile allows private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN work profile notification isn't redacted
assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -256,13 +316,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
// GIVEN work profile allows private notifications to show but the other users don't
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mWorkUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the work profile notification doesn't need to be redacted
assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -275,13 +337,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testWorkProfileRedacted_otherUsersNotRedacted() {
// GIVEN work profile doesn't allow private notifications to show but the other users do
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mWorkUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mCurrentUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the work profile notification needs to be redacted
assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
@@ -295,11 +359,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
public void testSecondaryUserNotRedacted_currentUserRedacted() {
// GIVEN secondary profile allows private notifications to show but the current user
// doesn't allow private notifications to show
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
mCurrentUser.id);
- mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
// THEN the secondary profile notification still needs to be redacted because the current
// user's setting takes precedence
@@ -323,6 +388,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testUpdateIsPublicMode() {
when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
mLockscreenUserManager.addNotificationStateChangedListener(listener);
@@ -330,24 +396,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
// first call explicitly sets user 0 to not public; notifies
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener).onNotificationStateChanged();
clearInvocations(listener);
// calling again has no changes; does not notify
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener, never()).onNotificationStateChanged();
// Calling again with keyguard now showing makes user 0 public; notifies
when(mKeyguardStateController.isShowing()).thenReturn(true);
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener).onNotificationStateChanged();
clearInvocations(listener);
// calling again has no changes; does not notify
mLockscreenUserManager.updatePublicMode();
+ TestableLooper.get(this).processAllMessages();
assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
verify(listener, never()).onNotificationStateChanged();
}
@@ -356,14 +426,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
public void testDevicePolicyDoesNotAllowNotifications() {
// User allows them
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides notifs on lockscreen
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -371,19 +441,18 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
- @Ignore("b/286230167")
@Test
public void testDevicePolicyDoesNotAllowNotifications_userAll() {
// User allows them
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -392,98 +461,105 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
- @Ignore("b/286230167")
public void testDevicePolicyDoesNotAllowNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- // TODO (b/286230167): enable assertion
verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@Test
public void testDevicePolicy_noPrivateNotifications() {
+ Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides sensitive content
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- // TODO (b/286230167): enable assertion. It's currently called 4 times.
- //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@Test
public void testDevicePolicy_noPrivateNotifications_userAll() {
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE);
+ NotificationEntry notifEntry = new NotificationEntryBuilder()
+ .setNotification(new Notification())
+ .setUser(UserHandle.ALL)
+ .build();
+ notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides sensitive content
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
- .setNotification(new Notification())
- .setUser(UserHandle.ALL)
- .build()));
+ assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));
}
@Test
public void testDevicePolicyPrivateNotifications_secondary() {
+ Mockito.clearInvocations(mDevicePolicyManager);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy hides sensitive content
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
.thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- // TODO (b/286230167): enable assertion. It's currently called 5 times.
- //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());
}
@Test
public void testHideNotifications_primary() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
@@ -491,16 +567,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testHideNotifications_secondary() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
}
- @Ignore("b/286230167")
@Test
public void testHideNotifications_workProfile() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));
}
@@ -508,67 +585,62 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Test
public void testHideNotifications_secondary_userSwitch() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- TestNotificationLockscreenUserManager lockscreenUserManager
- = new TestNotificationLockscreenUserManager(mContext);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
-
- lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
}
@Test
public void testShowNotifications_secondary_userSwitch() {
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- TestNotificationLockscreenUserManager lockscreenUserManager
- = new TestNotificationLockscreenUserManager(mContext);
- lockscreenUserManager.setUpWithPresenter(mPresenter);
-
- lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
+ mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
}
- @Ignore("b/286230167")
@Test
public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() {
+ // KeyguardManager does not allow notifications
+ when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
// DevicePolicy allows notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(0);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- // KeyguardManager does not
- when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
}
@Test
public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
// DevicePolicy allows notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(0);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
- // KeyguardManager does not
+ // KeyguardManager does not allow notifications
when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
+ // User allows notifications
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
+ // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
+ // callback, so it's only updated when the setting is
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
+
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
@@ -576,31 +648,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
public void testUserAllowsNotificationsInPublic_settingsChange() {
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
// User disables
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
- @Ignore("b/286230167")
@Test
public void testUserAllowsNotificationsInPublic_devicePolicyChange() {
// User allows notifications
mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
+ changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
// DevicePolicy disables notifications
when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
.thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class);
- when(pr.getSendingUserId()).thenReturn(mCurrentUser.id);
+ BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
+ 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
@@ -608,6 +679,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
}
+ @Test
+ public void testNewUserAdded() {
+ int newUserId = 14;
+ mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId);
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId))
+ .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
+
+ BroadcastReceiver broadcastReceiver =
+ mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+ final Bundle extras = new Bundle();
+ final Intent intent = new Intent(Intent.ACTION_USER_ADDED);
+ intent.putExtras(extras);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+ broadcastReceiver.onReceive(mContext, intent);
+
+ verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));
+
+ assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
+ assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
+ }
+
private class TestNotificationLockscreenUserManager
extends NotificationLockscreenUserManagerImpl {
public TestNotificationLockscreenUserManager(Context context) {
@@ -623,12 +716,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
(() -> mOverviewProxyService),
NotificationLockscreenUserManagerTest.this.mKeyguardManager,
mStatusBarStateController,
- Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(TestableLooper.get(
+ NotificationLockscreenUserManagerTest.this).getLooper()),
+ Handler.createAsync(TestableLooper.get(
+ NotificationLockscreenUserManagerTest.this).getLooper()),
+ mBackgroundExecutor,
mDeviceProvisionedController,
mKeyguardStateController,
mSettings,
mock(DumpManager.class),
- mock(LockPatternUtils.class));
+ mock(LockPatternUtils.class),
+ mFakeFeatureFlags);
}
public BroadcastReceiver getBaseBroadcastReceiverForTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 83f2a5d4fbdf..b922ab39912b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,6 +22,7 @@ import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.NotificationChannel;
import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
@@ -51,6 +53,9 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -96,6 +101,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Mock private UserTracker mUserTracker;
private final FakeSettings mSecureSettings = new FakeSettings();
private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
private NotificationEntry mEntry;
@@ -116,7 +122,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
mStatusBarStateController,
mUserTracker,
mSecureSettings,
- mGlobalSettings);
+ mGlobalSettings,
+ mFeatureFlags);
mKeyguardNotificationVisibilityProvider = component.getProvider();
for (CoreStartable startable : component.getCoreStartables().values()) {
startable.start();
@@ -424,6 +431,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void publicMode_settingsDisallow() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -433,12 +441,59 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
.thenReturn(false);
+ mEntry.setRanking(new RankingBuilder()
+ .setChannel(new NotificationChannel("1", "1", 4))
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+ .setKey(mEntry.getKey()).build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void publicMode_settingsDisallow_mainThread() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in public mode and settings are configured to disallow
+ // notifications in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+ when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
+ .thenReturn(false);
+
+ mEntry.setRanking(new RankingBuilder()
+ .setChannel(new NotificationChannel("1", "1", 4))
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+ .setKey(mEntry.getKey()).build());
+
// THEN filter out the entry
assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
}
@Test
public void publicMode_notifDisallowed() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+ NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_SECRET);
+ // GIVEN an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // WHEN the notification's user is in public mode and settings are configured to disallow
+ // notifications in public mode
+ when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+ mEntry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setChannel(channel)
+ .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void publicMode_notifDisallowed_mainThread() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -506,6 +561,54 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
}
@Test
+ public void notificationChannelVisibilityNoOverride() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+ // GIVEN a VISIBILITY_PRIVATE notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PRIVATE);
+ mEntry = entryBuilder.build();
+ // ranking says secret because of DPC or Setting
+ mEntry.setRanking(new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setVisibilityOverride(VISIBILITY_SECRET)
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void notificationChannelVisibilitySecret() {
+ mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+ // GIVEN a VISIBILITY_PRIVATE notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PRIVATE);
+ // And a VISIBILITY_SECRET NotificationChannel
+ NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH);
+ channel.setLockscreenVisibility(VISIBILITY_SECRET);
+ mEntry = entryBuilder.build();
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+ when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+ when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
+
+ mEntry.setRanking(new RankingBuilder(mEntry.getRanking())
+ .setChannel(channel)
+ .build());
+
+ // THEN hide the entry based on visibility.
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
public void notificationVisibilityPrivate() {
// GIVEN a VISIBILITY_PRIVATE notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
@@ -635,7 +738,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@BindsInstance SysuiStatusBarStateController statusBarStateController,
@BindsInstance UserTracker userTracker,
@BindsInstance SecureSettings secureSettings,
- @BindsInstance GlobalSettings globalSettings
+ @BindsInstance GlobalSettings globalSettings,
+ @BindsInstance FeatureFlagsClassic featureFlags
);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 03f5f005ee47..3556703a2fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -30,9 +30,11 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
+import com.android.systemui.res.R;
import org.junit.After;
import org.junit.Before;
@@ -51,8 +53,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final float OPAQUE = 1.f;
private static final float TRANSPARENT = 0.f;
- @Mock
- private Resources mResources;
+ @Mock private Resources mResources;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
@@ -80,7 +81,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
.mockStatic(BurnInHelperKt.class)
.startMocking();
- mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create();
+ mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
mClockPositionAlgorithm.loadDimens(mResources);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index e6f8c4861a94..9aafee4770de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
@@ -80,10 +82,14 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
@Mock
private lateinit var handler: Handler
+ private lateinit var featureFlags: FakeFeatureFlags
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ featureFlags = FakeFeatureFlags().apply {
+ set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
+ }
controller = UnlockedScreenOffAnimationController(
context,
wakefulnessLifecycle,
@@ -95,7 +101,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
interactionJankMonitor,
powerManager,
- handler = handler
+ handler = handler,
+ featureFlags,
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 812e91beee48..610fede3dfea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -60,6 +60,8 @@ class FakeMobileConnectionRepository(
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
+
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 57f97ec66a00..1f8cc54211e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -123,6 +123,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange = testCase.carrierNetworkChange,
roaming = testCase.roaming,
name = "demo name",
+ slice = testCase.slice,
)
fakeNetworkEventFlow.value = networkModel
@@ -142,6 +143,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
launch { conn.carrierName.collect {} }
launch { conn.isEmergencyOnly.collect {} }
launch { conn.dataConnectionState.collect {} }
+ launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
}
return job
}
@@ -165,6 +167,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+ assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
// TODO(b/261029387): check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -190,6 +193,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
val carrierNetworkChange: Boolean,
val roaming: Boolean,
val name: String,
+ val slice: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -200,7 +204,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
"activity=$activity, " +
"carrierNetworkChange=$carrierNetworkChange, " +
"roaming=$roaming, " +
- "name=$name)"
+ "name=$name," +
+ "slice=$slice)"
}
// Convenience for iterating test data and creating new cases
@@ -214,6 +219,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange: Boolean? = null,
roaming: Boolean? = null,
name: String? = null,
+ slice: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -225,6 +231,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
roaming = roaming ?: this.roaming,
name = name ?: this.name,
+ slice = slice ?: this.slice,
)
}
@@ -254,6 +261,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
// false first so the base case doesn't have roaming set (more common)
private val roaming = listOf(false, true)
private val names = listOf("name 1", "name 2")
+ private val slice = listOf(false, true)
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
@@ -291,6 +299,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange.first(),
roaming.first(),
names.first(),
+ slice.first(),
)
val tail =
@@ -303,6 +312,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
roaming.map { baseCase.modifiedBy(roaming = it) },
names.map { baseCase.modifiedBy(name = it) },
+ slice.map { baseCase.modifiedBy(slice = it) }
)
.flatten()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 2712b70a9745..d918fa8193bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -549,6 +549,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
launch { conn.carrierName.collect {} }
launch { conn.isEmergencyOnly.collect {} }
launch { conn.dataConnectionState.collect {} }
+ launch { conn.hasPrioritizedNetworkCapabilities.collect {} }
}
return job
}
@@ -574,6 +575,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
+ assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
// TODO(b/261029387) check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -599,6 +601,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(conn.isEmergencyOnly.value).isFalse()
assertThat(conn.isGsm.value).isFalse()
assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse()
job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index ede02d1fbc9c..1c21ebe05d84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.net.ConnectivityManager
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
@@ -80,6 +81,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
+ private val connectivityManager = mock<ConnectivityManager>()
private val subscriptionModel =
MutableStateFlow(
@@ -678,6 +680,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
+ connectivityManager,
telephonyManager,
systemUiCarrierConfig = mock(),
fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 8ef82c9fe7bc..ba6426586ebd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
@@ -85,7 +87,9 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -107,6 +111,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
@@ -144,6 +149,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
+ connectivityManager,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
@@ -904,6 +910,45 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isFalse()
}
+ @Test
+ fun hasPrioritizedCaps_defaultFalse() {
+ assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_trueWhenAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+ val callback: NetworkCallback =
+ withArgCaptor<NetworkCallback> {
+ verify(connectivityManager).registerNetworkCallback(any(), capture())
+ }
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities)
+
+ val callback: NetworkCallback =
+ withArgCaptor<NetworkCallback> {
+ verify(connectivityManager).registerNetworkCallback(any(), capture())
+ }
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+
+ callback.onLost(mock())
+
+ assertThat(latest).isFalse()
+ }
+
private inline fun <reified T> getTelephonyCallbackForType(): T {
return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 852ed2054fcd..889f60a08766 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.net.ConnectivityManager
import android.telephony.ServiceState
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.CarrierNetworkListener
@@ -96,6 +97,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
private lateinit var connectionsRepo: FakeMobileConnectionsRepository
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
@@ -129,6 +131,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
subscriptionModel,
DEFAULT_NAME,
SEP,
+ connectivityManager,
telephonyManager,
systemUiCarrierConfig,
fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 9148c7580296..18ba6c4f5d9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -180,6 +180,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
MobileConnectionRepositoryImpl.Factory(
context,
fakeBroadcastDispatcher,
+ connectivityManager,
telephonyManager = telephonyManager,
bgDispatcher = dispatcher,
logger = logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index de2b6a850e03..5f4d7bf6f371 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -48,6 +48,8 @@ class FakeMobileIconInteractor(
NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
)
+ override val showSliceAttribution = MutableStateFlow(false)
+
override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
override val carrierName = MutableStateFlow("demo mode")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 218fd33ac0fc..3936bedc1059 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -22,13 +22,15 @@ import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
import android.view.View
+import android.widget.FrameLayout
import android.widget.ImageView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
@@ -58,8 +60,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
private lateinit var testableLooper: TestableLooper
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
- @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var viewLogger: MobileViewLogger
@Mock private lateinit var constants: ConnectivityConstants
@@ -246,7 +248,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
testableLooper.processAllMessages()
val color = 0x12345678
- view.onDarkChanged(arrayListOf(), 1.0f, color)
+ val contrast = 0x12344321
+ view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -267,7 +270,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
testableLooper.processAllMessages()
val color = 0x23456789
- view.setStaticDrawableColor(color)
+ val contrast = 0x12344321
+ view.setStaticDrawableColor(color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -275,6 +279,35 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
ViewUtils.detachView(view)
}
+ @Test
+ fun colorChange_layersUpdateWithContrast() {
+ // Allow the slice, and set it to visible. This cause us to use special color logic
+ flags.set(Flags.NEW_NETWORK_SLICE_UI, true)
+ interactor.showSliceAttribution.value = true
+ createViewModel()
+
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x23456789
+ val contrast = 0x12344321
+ view.setStaticDrawableColor(color, contrast)
+
+ testableLooper.processAllMessages()
+
+ assertThat(view.getNetTypeContainer().backgroundTintList).isEqualTo(color.colorState())
+ assertThat(view.getNetTypeView().imageTintList).isEqualTo(contrast.colorState())
+
+ ViewUtils.detachView(view)
+ }
+
private fun View.getGroupView(): View {
return this.requireViewById(R.id.mobile_group)
}
@@ -283,10 +316,20 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
return this.requireViewById(R.id.mobile_signal)
}
+ private fun View.getNetTypeContainer(): FrameLayout {
+ return this.requireViewById(R.id.mobile_type_container)
+ }
+
+ private fun View.getNetTypeView(): ImageView {
+ return this.requireViewById(R.id.mobile_type)
+ }
+
private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot)
}
+ private fun Int.colorState() = ColorStateList.valueOf(this)
+
private fun createViewModel() {
viewModelCommon =
MobileIconViewModel(
@@ -294,6 +337,7 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
viewModel = QsMobileIconViewModel(viewModelCommon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 187832908e2b..1d5487f31e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
@@ -47,12 +50,14 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
private lateinit var commonImpl: MobileIconViewModelCommon
private lateinit var homeIcon: HomeMobileIconViewModel
@@ -65,6 +70,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
private val connectivityRepository = FakeConnectivityRepository()
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
@@ -133,6 +139,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 796d5ec3dd42..c831e62dd709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
@@ -26,7 +27,12 @@ import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -54,12 +60,14 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
class MobileIconViewModelTest : SysuiTestCase() {
private var connectivityRepository = FakeConnectivityRepository()
@@ -74,6 +82,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -595,6 +604,46 @@ class MobileIconViewModelTest : SysuiTestCase() {
containerJob.cancel()
}
+ @Test
+ fun netTypeBackground_flagOff_isNull() =
+ testScope.runTest {
+ flags.set(NEW_NETWORK_SLICE_UI, false)
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun netTypeBackground_flagOn_nullWhenNoPrioritizedCapabilities() =
+ testScope.runTest {
+ flags.set(NEW_NETWORK_SLICE_UI, true)
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun netTypeBackground_flagOn_notNullWhenPrioritizedCapabilities() =
+ testScope.runTest {
+ flags.set(NEW_NETWORK_SLICE_UI, true)
+ createAndSetViewModel()
+
+ val latest by collectLastValue(underTest.networkTypeBackground)
+
+ repository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest)
+ .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null))
+ }
+
private fun createAndSetViewModel() {
underTest =
MobileIconViewModel(
@@ -602,6 +651,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index eb6f2f81fde4..f3e334ed8a22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -41,15 +44,18 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@RunWith(AndroidJUnit4::class)
class MobileIconsViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@@ -77,6 +83,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
interactor,
airplaneModeInteractor,
constants,
+ flags,
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index e44ff8e21270..28d632d9fcea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -41,6 +41,7 @@ class FakeConnectivityRepository : ConnectivityRepository {
* setting mobile connected && validated, since the default state is disconnected && not
* validated
*/
+ @JvmOverloads
fun setMobileConnected(
default: Boolean = true,
validated: Boolean = true,
@@ -53,6 +54,7 @@ class FakeConnectivityRepository : ConnectivityRepository {
}
/** Similar convenience method for ethernet */
+ @JvmOverloads
fun setEthernetConnected(
default: Boolean = true,
validated: Boolean = true,
@@ -64,6 +66,7 @@ class FakeConnectivityRepository : ConnectivityRepository {
)
}
+ @JvmOverloads
fun setWifiConnected(
default: Boolean = true,
validated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index b4039d906810..028a58c6e1b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -77,9 +77,10 @@ class ModernStatusBarViewTest : SysuiTestCase() {
fun onDarkChanged_bindingReceivesIconAndDecorTint() {
val view = createAndInitView()
- view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678)
+ view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.contrastTint).isEqualTo(0x12344321)
assertThat(binding.decorTint).isEqualTo(0x12345678)
}
@@ -87,9 +88,10 @@ class ModernStatusBarViewTest : SysuiTestCase() {
fun setStaticDrawableColor_bindingReceivesIconTint() {
val view = createAndInitView()
- view.setStaticDrawableColor(0x12345678)
+ view.setStaticDrawableColor(0x12345678, 0x12344321)
assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.contrastTint).isEqualTo(0x12344321)
}
@Test
@@ -144,13 +146,15 @@ class ModernStatusBarViewTest : SysuiTestCase() {
inner class TestBinding : ModernStatusBarViewBinding {
var iconTint: Int? = null
+ var contrastTint: Int? = null
var decorTint: Int? = null
var onVisibilityStateChangedCalled: Boolean = false
var shouldIconBeVisibleInternal: Boolean = true
- override fun onIconTintChanged(newTint: Int) {
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
iconTint = newTint
+ this.contrastTint = contrastTint
}
override fun onDecorTintChanged(newTint: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index a27f8990dec1..d75a45247cbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,9 +25,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -229,7 +229,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
testableLooper.processAllMessages()
val color = 0x12345678
- view.onDarkChanged(arrayListOf(), 1.0f, color)
+ val contrast = 0x12344321
+ view.onDarkChangedWithContrast(arrayListOf(), color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
@@ -244,7 +245,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
testableLooper.processAllMessages()
val color = 0x23456789
- view.setStaticDrawableColor(color)
+ val contrast = 0x12344321
+ view.setStaticDrawableColor(color, contrast)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index 1250228e2d37..d33806e131d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import com.android.systemui.flags.FakeFeatureFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -77,11 +78,13 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() {
private lateinit var view: FrameLayout
private lateinit var testableLooper: TestableLooper
private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ featureFlags = FakeFeatureFlags()
view = LayoutInflater.from(context)
.inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -98,6 +101,7 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() {
dozeParameters,
screenOffAnimationController,
userSwitchDialogController,
+ featureFlags,
uiEventLogger)
ViewUtils.attachView(view)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index f7e0120d3843..6ef812be0350 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui;
+import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -25,6 +27,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
import android.testing.LeakCheck;
import android.testing.TestWithLooperRule;
@@ -68,6 +71,9 @@ public abstract class SysuiTestCase {
new AndroidXAnimatorIsolationRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), getLeakCheck());
@Rule
@@ -88,6 +94,10 @@ public abstract class SysuiTestCase {
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
+ // Set the value of a single gantry flag inside the com.android.systemui package to
+ // ensure all flags in that package are faked (and thus require a value to be set).
+ mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG);
+
mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext);
mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
mContext,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 10b284a03a59..5dcc7423ecc6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -47,7 +47,7 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
}
override fun getDimensionPixelSize(id: Int): Int {
- throw IllegalStateException("Don't use for tests")
+ return 0
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index e91e9559fa1e..852611230623 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -34,7 +34,7 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
get() = _isFingerprintAuthCurrentlyAllowed
private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
get() = _isFaceAuthEnrolledAndEnabled
private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
index 272d686e974b..272d686e974b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 21d09792f1c7..b403a7fe8f12 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -404,5 +404,9 @@ message SystemMessage {
// Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
NOTE_CSD_LOWER_AUDIO = 1007;
+
+ // Notify the user about external display events related to screenshot.
+ // Package: com.android.systemui
+ NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
}
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 10ac2ebc9b2f..f09cb19d0c1d 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -48,3 +48,17 @@ flag {
description: "Stops using the deprecated PackageListObserver."
bug: "304561459"
}
+
+flag {
+ name: "scan_packages_without_lock"
+ namespace: "accessibility"
+ description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+ bug: "295969873"
+}
+
+flag {
+ name: "reduce_touch_exploration_sensitivity"
+ namespace: "accessibility"
+ description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
+ bug: "303677860"
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aa6d800510e1..8c1d444006bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -290,14 +290,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
- private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
- new ArrayList<>();
-
private final IntArray mTempIntArray = new IntArray(0);
private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
new RemoteCallbackList<>();
+ private PackageMonitor mPackageMonitor;
+
@VisibleForTesting
final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
@@ -531,6 +530,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
disableAccessibilityMenuToMigrateIfNeeded();
}
+ /**
+ * Returns if the current thread is holding {@link #mLock}. Used for testing
+ * deadlock bug fixes.
+ *
+ * <p><strong>Warning:</strong> this should not be used for production logic
+ * because by the time you receive an answer it may no longer be valid.
+ * </p>
+ */
+ @VisibleForTesting
+ boolean unsafeIsLockHeld() {
+ return Thread.holdsLock(mLock);
+ }
+
@Override
public int getCurrentUserIdLocked() {
return mCurrentUserId;
@@ -690,6 +702,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private void onSomePackagesChangedLocked(
+ @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+ @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ // Reload the installed services since some services may have different attributes
+ // or resolve info (does not support equals), etc. Remove them then to force reload.
+ userState.mInstalledServices.clear();
+ if (readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) {
+ onUserStateChangedLocked(userState);
+ }
+ }
+
private void onPackageRemovedLocked(String packageName) {
final AccessibilityUserState userState = getCurrentUserState();
final Predicate<ComponentName> filter =
@@ -721,8 +746,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ @VisibleForTesting
+ PackageMonitor getPackageMonitor() {
+ return mPackageMonitor;
+ }
+
private void registerBroadcastReceivers() {
- PackageMonitor monitor = new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
@@ -730,13 +760,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
FLAGS_PACKAGE_BROADCAST_RECEIVER);
}
+ final int userId = getChangingUserId();
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+ if (Flags.scanPackagesWithoutLock()) {
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+ }
synchronized (mLock) {
// Only the profile parent can install accessibility services.
// Therefore we ignore packages from linked profiles.
- if (getChangingUserId() != mCurrentUserId) {
+ if (userId != mCurrentUserId) {
return;
}
- onSomePackagesChangedLocked();
+ if (Flags.scanPackagesWithoutLock()) {
+ onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+ parsedAccessibilityShortcutInfos);
+ } else {
+ onSomePackagesChangedLocked();
+ }
}
}
@@ -751,8 +793,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
FLAGS_PACKAGE_BROADCAST_RECEIVER,
"packageName=" + packageName + ";uid=" + uid);
}
+ final int userId = getChangingUserId();
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+ if (Flags.scanPackagesWithoutLock()) {
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+ }
synchronized (mLock) {
- final int userId = getChangingUserId();
if (userId != mCurrentUserId) {
return;
}
@@ -765,8 +813,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// Reloads the installed services info to make sure the rebound service could
// get a new one.
userState.mInstalledServices.clear();
- final boolean configurationChanged =
- readConfigurationForUserStateLocked(userState);
+ final boolean configurationChanged;
+ if (Flags.scanPackagesWithoutLock()) {
+ configurationChanged = readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+ } else {
+ configurationChanged = readConfigurationForUserStateLocked(userState);
+ }
if (reboundAService || configurationChanged) {
onUserStateChangedLocked(userState);
}
@@ -839,7 +892,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
};
// package changes
- monitor.register(mContext, null, UserHandle.ALL, true);
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
if (!Flags.deprecatePackageListObserver()) {
final PackageManagerInternal pm = LocalServices.getService(
@@ -1831,8 +1884,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mA11yWindowManager.onTouchInteractionEnd();
}
- private void switchUser(int userId) {
+ @VisibleForTesting
+ void switchUser(int userId) {
mMagnificationController.updateUserIdIfNeeded(userId);
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
+ if (Flags.scanPackagesWithoutLock()) {
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
+ }
synchronized (mLock) {
if (mCurrentUserId == userId && mInitialized) {
return;
@@ -1857,7 +1917,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mCurrentUserId = userId;
AccessibilityUserState userState = getCurrentUserStateLocked();
- readConfigurationForUserStateLocked(userState);
+ if (Flags.scanPackagesWithoutLock()) {
+ readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
+ } else {
+ readConfigurationForUserStateLocked(userState);
+ }
mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
// Even if reading did not yield change, we have to update
// the state since the context in which the current user
@@ -2105,8 +2170,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
- mTempAccessibilityServiceInfoList.clear();
+ /**
+ * Finds packages that provide AccessibilityService interfaces, and parses
+ * their metadata XML to build up {@link AccessibilityServiceInfo} objects.
+ *
+ * <p>
+ * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+ * stall, so this method should not be called while holding a lock.
+ * </p>
+ */
+ private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) {
+ List<AccessibilityServiceInfo> result = new ArrayList<>();
int flags = PackageManager.GET_SERVICES
| PackageManager.GET_META_DATA
@@ -2114,12 +2188,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (userState.getBindInstantServiceAllowedLocked()) {
- flags |= PackageManager.MATCH_INSTANT;
+ synchronized (mLock) {
+ if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) {
+ flags |= PackageManager.MATCH_INSTANT;
+ }
}
List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
- new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId);
+ new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId);
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
@@ -2132,40 +2208,60 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
AccessibilityServiceInfo accessibilityServiceInfo;
try {
accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
- if (!accessibilityServiceInfo.isWithinParcelableSize()) {
- Slog.e(LOG_TAG, "Skipping service "
- + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
- + " because service info size is larger than safe parcelable limits.");
- continue;
- }
- if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
- // Restore the crashed attribute.
- accessibilityServiceInfo.crashed = true;
- }
- mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
} catch (XmlPullParserException | IOException xppe) {
Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
+ continue;
+ }
+ if (!accessibilityServiceInfo.isWithinParcelableSize()) {
+ Slog.e(LOG_TAG, "Skipping service "
+ + accessibilityServiceInfo.getResolveInfo().getComponentInfo()
+ + " because service info size is larger than safe parcelable limits.");
+ continue;
}
+ result.add(accessibilityServiceInfo);
}
+ return result;
+ }
- if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+ private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState,
+ @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) {
+ for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) {
+ AccessibilityServiceInfo accessibilityServiceInfo =
+ parsedAccessibilityServiceInfos.get(i);
+ if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) {
+ // Restore the crashed attribute.
+ accessibilityServiceInfo.crashed = true;
+ }
+ }
+
+ if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
userState.mInstalledServices.clear();
- userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
- mTempAccessibilityServiceInfoList.clear();
+ userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
return true;
}
-
- mTempAccessibilityServiceInfoList.clear();
return false;
}
- private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
- final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
- .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
- mContext, mCurrentUserId);
- if (!shortcutInfos.equals(userState.mInstalledShortcuts)) {
+ /**
+ * Returns the {@link AccessibilityShortcutInfo}s of the installed
+ * accessibility shortcut targets for the given user.
+ *
+ * <p>
+ * <strong>Note:</strong> XML parsing is a resource-heavy operation that may
+ * stall, so this method should not be called while holding a lock.
+ * </p>
+ */
+ private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) {
+ // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager.
+ return AccessibilityManager.getInstance(mContext)
+ .getInstalledAccessibilityShortcutListAsUser(mContext, userId);
+ }
+
+ private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+ if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
userState.mInstalledShortcuts.clear();
- userState.mInstalledShortcuts.addAll(shortcutInfos);
+ userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
return true;
}
return false;
@@ -2890,9 +2986,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.setFilterKeyEventsEnabledLocked(false);
}
+ // ErrorProne doesn't understand that this method is only called while locked,
+ // returning an error for accessing mCurrentUserId.
+ @SuppressWarnings("GuardedBy")
private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
- boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
- somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
+ return readConfigurationForUserStateLocked(userState,
+ parseAccessibilityServiceInfos(mCurrentUserId),
+ parseAccessibilityShortcutInfos(mCurrentUserId));
+ }
+
+ private boolean readConfigurationForUserStateLocked(
+ AccessibilityUserState userState,
+ List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
+ List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
+ boolean somethingChanged = readInstalledAccessibilityServiceLocked(
+ userState, parsedAccessibilityServiceInfos);
+ somethingChanged |= readInstalledAccessibilityShortcutLocked(
+ userState, parsedAccessibilityShortcutInfos);
somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c4184854e690..fc8d4f89e6a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -882,10 +882,22 @@ public class TouchExplorer extends BaseEventStreamTransformation
final int pointerIndex = event.findPointerIndex(pointerId);
switch (event.getPointerCount()) {
case 1:
- // Touch exploration.
+ // Touch exploration.
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
- mDispatcher.sendMotionEvent(
- event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+ if (Flags.reduceTouchExplorationSensitivity()
+ && mState.getLastInjectedHoverEvent() != null) {
+ final MotionEvent lastEvent = mState.getLastInjectedHoverEvent();
+ final float deltaX = lastEvent.getX() - rawEvent.getX();
+ final float deltaY = lastEvent.getY() - rawEvent.getY();
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta > mTouchSlop) {
+ mDispatcher.sendMotionEvent(
+ event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+ }
+ } else {
+ mDispatcher.sendMotionEvent(
+ event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
+ }
break;
case 2:
if (mGestureDetector.isMultiFingerGesturesEnabled()
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 72242d265e79..e4f1d3acce6d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -363,6 +363,7 @@ public final class AutofillManagerService
case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:
case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:
case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK:
+ case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION:
setDeviceConfigProperties();
break;
case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8a2aa616f8e6..4298c079a63e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -122,9 +122,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private static final String TAG = "VirtualDeviceImpl";
/**
- * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent
- * with virtual displays created via {@link DisplayManager} and allow for the creation of
- * private, auto-mirror, and fixed orientation displays since
+ * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent
+ * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow
+ * for the creation of private, auto-mirror, and fixed orientation displays since
* {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
*
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC
@@ -632,6 +632,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
super.setDevicePolicy_enforcePermission();
+ if (!Flags.dynamicPolicy()) {
+ return;
+ }
+
switch (policyType) {
case POLICY_TYPE_RECENTS:
synchronized (mVirtualDeviceLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 1a8dd3a7316e..ee41a69c6f4c 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -28,6 +28,7 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
+import static android.view.contentprotection.flags.Flags.parseGroupsConfigEnabled;
import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST;
import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL;
@@ -115,6 +116,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -142,6 +144,9 @@ public class ContentCaptureManagerService extends
private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10;
private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024;
+ private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP = ";";
+ private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE = ",";
+
// Needed to pass checkstyle_hook as names are too long for one line.
private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST =
CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST;
@@ -957,14 +962,40 @@ public class ContentCaptureManagerService extends
return mContentCaptureManagerServiceStub;
}
- /** @hide */
+ /**
+ * Parses a simple config in format "group;group" where each "group" is itself in the format of
+ * "string1,string2", eg:
+ *
+ * <p>"a" -> [["a"]]
+ *
+ * <p>"a,b" -> [["a", "b"]]
+ *
+ * <p>"a,b;c;d,e" -> [["a", "b"], ["c"], ["d", "e"]]
+ *
+ * @hide
+ */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) {
if (verbose) {
Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config);
}
- return Collections.emptyList();
+ if (!parseGroupsConfigEnabled()) {
+ return Collections.emptyList();
+ }
+ if (config == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(config.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP))
+ .map(this::parseContentProtectionGroupConfigValues)
+ .filter(group -> !group.isEmpty())
+ .toList();
+ }
+
+ private List<String> parseContentProtectionGroupConfigValues(@NonNull String group) {
+ return Arrays.stream(group.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE))
+ .filter(value -> !value.isEmpty())
+ .toList();
}
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 962f38f10b5d..beea063221fb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -214,9 +214,12 @@ class StorageManagerService extends IStorageManager.Stub
// external storage service.
public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
- /** Extended timeout for the system server watchdog. */
+ /** Extended timeout for the system server watchdog. */
private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000;
+ /** Extended timeout for the system server watchdog for vold#partition operation. */
+ private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
+
@GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
@@ -2338,6 +2341,8 @@ class StorageManagerService extends IStorageManager.Stub
try {
// TODO(b/135341433): Remove cautious logging when FUSE is stable
Slog.i(TAG, "Mounting volume " + vol);
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
@Override
public boolean onVolumeChecking(FileDescriptor fd, String path,
@@ -2463,10 +2468,12 @@ class StorageManagerService extends IStorageManager.Stub
@android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
@Override
public void partitionPublic(String diskId) {
-
super.partitionPublic_enforcePermission();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2483,6 +2490,9 @@ class StorageManagerService extends IStorageManager.Stub
enforceAdminUser();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
@@ -2499,6 +2509,9 @@ class StorageManagerService extends IStorageManager.Stub
enforceAdminUser();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+
+ Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 5fb889a23fc5..1650a96a4012 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5309,7 +5309,7 @@ public class AccountManagerService
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
}
- long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE;
+ long flags = Context.BIND_AUTO_CREATE;
if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) {
flags |= Context.BIND_ALLOW_INSTANT;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0956c6ded013..5f1a7e7e8123 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3678,8 +3678,8 @@ public final class ActiveServices {
|| (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0;
- final boolean filterOutQuarantined =
- (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0;
+ final boolean matchQuarantined =
+ (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;
ProcessRecord attributedApp = null;
if (sdkSandboxClientAppUid > 0) {
@@ -3689,7 +3689,7 @@ public final class ActiveServices {
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
isBindExternal, allowInstant, null /* fgsDelegateOptions */,
- inSharedIsolatedProcess, filterOutQuarantined);
+ inSharedIsolatedProcess, matchQuarantined);
if (res == null) {
return 0;
}
@@ -4202,7 +4202,7 @@ public final class ActiveServices {
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
allowInstant, fgsDelegateOptions, inSharedIsolatedProcess,
- false /* filterOutQuarantined */);
+ false /* matchQuarantined */);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -4211,7 +4211,7 @@ public final class ActiveServices {
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
- boolean inSharedIsolatedProcess, boolean filterOutQuarantined) {
+ boolean inSharedIsolatedProcess, boolean matchQuarantined) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -4333,8 +4333,8 @@ public final class ActiveServices {
if (allowInstant) {
flags |= PackageManager.MATCH_INSTANT;
}
- if (filterOutQuarantined) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
+ if (matchQuarantined) {
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
}
// TODO: come back and remove this assumption to triage all services
ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b43b986064fe..31817f1c427d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -58,7 +58,6 @@ import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTAT
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
@@ -14295,8 +14294,7 @@ public class ActivityManagerService extends IActivityManager.Stub
private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
int callingUid, int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
- long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING
- | FILTER_OUT_QUARANTINED_COMPONENTS;
+ long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
List<ResolveInfo> receivers = null;
HashSet<ComponentName> singleUserReceivers = null;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2249607863d5..0ab81a5e4eac 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -91,6 +91,7 @@ import android.telephony.ModemActivityInfo;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
+import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.StatsEvent;
@@ -99,8 +100,10 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.CpuScalingPolicyReader;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats;
@@ -114,16 +117,21 @@ import com.android.server.Watchdog;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.optimization.Flags;
+import com.android.server.power.stats.AggregatedPowerStatsConfig;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.PowerStatsAggregator;
+import com.android.server.power.stats.PowerStatsScheduler;
+import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.wakeups.CpuWakeupStats;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -136,6 +144,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -153,20 +162,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub
static final String TAG = "BatteryStatsService";
static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason";
static final boolean DBG = false;
- private static final boolean BATTERY_USAGE_STORE_ENABLED = true;
private static IBatteryStats sService;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
+ private final MonotonicClock mMonotonicClock;
private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig;
final BatteryStatsImpl mStats;
final CpuWakeupStats mCpuWakeupStats;
- private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+ private final PowerStatsStore mPowerStatsStore;
+ private final PowerStatsAggregator mPowerStatsAggregator;
+ private final PowerStatsScheduler mPowerStatsScheduler;
private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
private final Context mContext;
private final BatteryExternalStatsWorker mWorker;
private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private final AtomicFile mConfigFile;
+
private volatile boolean mMonitorEnabled = true;
private native void getRailEnergyPowerStats(RailStats railStats);
@@ -376,6 +389,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));
mPowerProfile = new PowerProfile(context);
mCpuScalingPolicies = new CpuScalingPolicyReader().read();
@@ -391,23 +405,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub
.setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
.setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
.build();
- mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this,
- this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies);
+ mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
+ systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+ mCpuScalingPolicies);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
mStats.startTrackingSystemServerCpuTime();
- if (BATTERY_USAGE_STORE_ENABLED) {
- mBatteryUsageStatsStore =
- new BatteryUsageStatsStore(context, mStats, systemDir, mHandler);
- } else {
- mBatteryUsageStatsStore = null;
- }
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig();
+ mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
- mBatteryUsageStatsStore);
+ mPowerStatsStore);
+ mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
+ mStats.getHistory());
+ final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+ final long powerStatsAggregationPeriod = context.getResources().getInteger(
+ com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+ mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+ aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
+ Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);
mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
+ mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
+ }
+
+ private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ return config;
}
/**
@@ -466,9 +500,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
*/
public void onSystemReady() {
mStats.onSystemReady();
- if (BATTERY_USAGE_STORE_ENABLED) {
- mBatteryUsageStatsStore.onSystemReady();
- }
+ mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());
}
private final class LocalService extends BatteryStatsInternal {
@@ -639,6 +671,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
// Shutdown the thread we made.
mWorker.shutdown();
+
+ // To insure continuity, write the monotonic timeshift after writing the last history event
+ mMonotonicClock.write();
}
public static IBatteryStats getService() {
@@ -892,12 +927,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
- if (!BATTERY_USAGE_STORE_ENABLED) {
- return StatsManager.PULL_SKIP;
- }
-
- final long sessionStart = mBatteryUsageStatsStore
- .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
+ final long sessionStart =
+ getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
synchronized (mStats) {
sessionEnd = mStats.getStartClockTime();
@@ -910,8 +941,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
.aggregateSnapshots(sessionStart, sessionEnd)
.build();
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
- mBatteryUsageStatsStore
- .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
+ setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -2641,7 +2671,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
private void dumpAggregatedStats(PrintWriter pw) {
- mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+ mPowerStatsScheduler.aggregateAndDumpPowerStats(pw);
+ }
+
+ private void dumpPowerStatsStore(PrintWriter pw) {
+ mPowerStatsStore.dump(new IndentingPrintWriter(pw, " "));
+ }
+
+ private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) {
+ mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, " "));
}
private void dumpMeasuredEnergyStats(PrintWriter pw) {
@@ -2789,7 +2827,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.resetAllStatsAndHistoryLocked(
BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
- mBatteryUsageStatsStore.removeAllSnapshots();
+ mPowerStatsStore.reset();
pw.println("Battery stats and history reset.");
noOutput = true;
}
@@ -2891,6 +2929,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("--aggregated".equals(arg)) {
dumpAggregatedStats(pw);
return;
+ } else if ("--store".equals(arg)) {
+ dumpPowerStatsStore(pw);
+ return;
+ } else if ("--store-toc".equals(arg)) {
+ dumpPowerStatsStoreTableOfContents(pw);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2951,7 +2995,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- mBatteryStatsConfig,
+ mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies);
@@ -2993,7 +3037,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- mBatteryStatsConfig,
+ mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies);
@@ -3360,6 +3404,52 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
+ "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
+
+ /**
+ * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
+ * in persistent file.
+ */
+ public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
+ synchronized (mConfigFile) {
+ Properties props = new Properties();
+ try (InputStream in = mConfigFile.openRead()) {
+ props.load(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+ }
+ props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
+ String.valueOf(timestamp));
+ FileOutputStream out = null;
+ try {
+ out = mConfigFile.startWrite();
+ props.store(out, "Statsd atom pull timestamps");
+ mConfigFile.finishWrite(out);
+ } catch (IOException e) {
+ mConfigFile.failWrite(out);
+ Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
+ * statsd atom pull.
+ */
+ public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
+ synchronized (mConfigFile) {
+ Properties props = new Properties();
+ try (InputStream in = mConfigFile.openRead()) {
+ props.load(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+ }
+ return Long.parseLong(
+ props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
+ }
+ }
+
/**
* Sets battery AC charger to enabled/disabled, and freezes the battery state.
*/
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3771c05a294e..f02b8c737f90 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -511,8 +511,8 @@ class ProcessRecord implements WindowProcessListener {
pw.print(prefix); pw.print("pid="); pw.println(mPid);
pw.print(prefix); pw.print("lastActivityTime=");
TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
- pw.print(prefix); pw.print("startUptimeTime=");
- TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw);
+ pw.print(prefix); pw.print("startUpTime=");
+ TimeUtils.formatDuration(mStartUptime, nowUptime, pw);
pw.print(prefix); pw.print("startElapsedTime=");
TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw);
pw.println();
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 1ba1f55af71b..16e3fdf2a6ab 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -163,9 +163,12 @@ public class SettingsToPropertiesMapper {
"wear_system_health",
"wear_systems",
"window_surfaces",
- "windowing_frontend"
+ "windowing_frontend",
};
+ public static final String NAMESPACE_REBOOT_STAGING = "staged";
+ public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*";
+
private final String[] mGlobalSettings;
private final String[] mDeviceConfigScopes;
@@ -261,6 +264,22 @@ public class SettingsToPropertiesMapper {
}
});
}
+
+ // add sys prop sync callback for staged flag values
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_REBOOT_STAGING,
+ AsyncTask.THREAD_POOL_EXECUTOR,
+ (DeviceConfig.Properties properties) -> {
+ String scope = properties.getNamespace();
+ for (String key : properties.getKeyset()) {
+ String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key);
+ if (aconfigPropertyName == null) {
+ log("unable to construct system property for " + scope + "/" + key);
+ return;
+ }
+ setProperty(aconfigPropertyName, properties.getString(key, null));
+ }
+ });
}
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -332,6 +351,35 @@ public class SettingsToPropertiesMapper {
}
/**
+ * system property name constructing rule for staged aconfig flags, the flag name
+ * is in the form of [namespace]*[actual flag name], we should push the following
+ * to system properties
+ * "next_boot.[actual sys prop name]".
+ * If the name contains invalid characters or substrings for system property name,
+ * will return null.
+ * @param flagName
+ * @return
+ */
+ @VisibleForTesting
+ static String makeAconfigFlagStagedPropertyName(String flagName) {
+ int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+ if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+ log("invalid staged flag: " + flagName);
+ return null;
+ }
+
+ String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+ flagName.substring(0, idx), flagName.substring(idx+1));
+
+ if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
+ || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
+ return null;
+ }
+
+ return propertyName;
+ }
+
+ /**
* system property name constructing rule for aconfig flags:
* "persist.device_config.aconfig_flags.[category_name].[flag_name]".
* If the name contains invalid characters or substrings for system property name,
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b537e0eae548..b12d831ffe24 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -6,3 +6,10 @@ flag {
description: "This flag controls tunscany virtual HAL feature"
bug: "294254230"
}
+
+flag {
+ name: "de_hidl"
+ namespace: "biometrics_framework"
+ description: "feature flag for biometrics de-hidl"
+ bug: "287332354"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 78c95ad4576b..a47135fd032f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,6 +35,7 @@ import android.util.EventLog;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -116,7 +117,24 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
@LockoutTracker.LockoutMode
public int handleFailedAttempt(int userId) {
- return LockoutTracker.LOCKOUT_NONE;
+ if (Flags.deHidl()) {
+ if (mLockoutTracker != null) {
+ mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
+ }
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(userId);
+ final PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(getSensorId());
+ if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+ performanceTracker.incrementPermanentLockoutForUser(userId);
+ } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+ performanceTracker.incrementTimedLockoutForUser(userId);
+ }
+
+ return lockoutMode;
+ } else {
+ return LockoutTracker.LOCKOUT_NONE;
+ }
}
protected long getStartTimeMs() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 8a54ae5a6fea..037ea38a2d17 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -586,4 +586,13 @@ public class BiometricScheduler {
}
}, 10000);
}
+
+ /**
+ * Handle stop user client when user switching occurs.
+ */
+ public void onUserStopped() {}
+
+ public Handler getHandler() {
+ return mHandler;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
index 95c49032c029..35e9bcbbf085 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java
@@ -32,6 +32,7 @@ public class LockoutCache implements LockoutTracker {
mUserLockoutStates = new SparseIntArray();
}
+ @Override
public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
Slog.d(TAG, "Lockout for user: " + userId + " is " + mode);
synchronized (this) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
index 4a59c9df9bef..8271380010e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java
@@ -35,4 +35,7 @@ public interface LockoutTracker {
@interface LockoutMode {}
@LockoutMode int getLockoutModeForUser(int userId);
+ void setLockoutModeForUser(int userId, @LockoutMode int mode);
+ default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {}
+ default void addFailedAttemptForUser(int userId) {}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 694dfd28d0cc..80754702415a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -165,6 +165,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {
}
}
+ @Override
public void onUserStopped() {
if (mStopUserClient == null) {
Slog.e(getTag(), "Unexpected onUserStopped");
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index cc0022703745..e5d4a635876d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -31,6 +31,11 @@ public class LockoutHalImpl implements LockoutTracker {
return mCurrentUserLockoutMode;
}
+ @Override
+ public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+ setCurrentUserLockoutMode(mode);
+ }
+
public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) {
mCurrentUserLockoutMode = lockoutMode;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
index 573c20fe041b..d149f5215a7a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
@@ -38,7 +38,7 @@ import android.util.Slog;
/**
* Utilities for converting from hardware to framework-defined AIDL models.
*/
-final class AidlConversionUtils {
+public final class AidlConversionUtils {
private static final String TAG = "AidlConversionUtils";
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
new file mode 100644
index 000000000000..57f5b34c197a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.AuthenticationFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.hardware.face.Face;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface HardwareUnavailableCallback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
+ private static final String TAG = "AidlResponseHandler";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final BiometricScheduler mScheduler;
+ private final int mSensorId;
+ private final int mUserId;
+ @NonNull
+ private final LockoutTracker mLockoutCache;
+ @NonNull
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+ @NonNull
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ mContext = context;
+ mScheduler = scheduler;
+ mSensorId = sensorId;
+ mUserId = userId;
+ mLockoutCache = lockoutTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ @Override
+ public void onChallengeGenerated(long challenge) {
+ handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
+ mUserId, challenge), null);
+ }
+
+ @Override
+ public void onChallengeRevoked(long challenge) {
+ handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
+ mUserId, challenge), null);
+ }
+
+ @Override
+ public void onAuthenticationFrame(AuthenticationFrame frame) {
+ handleResponse(FaceAuthenticationClient.class, (c) -> {
+ if (frame == null) {
+ Slog.e(TAG, "Received null enrollment frame for face authentication client.");
+ return;
+ }
+ c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
+ }, null);
+ }
+
+ @Override
+ public void onEnrollmentFrame(EnrollmentFrame frame) {
+ handleResponse(FaceEnrollClient.class, (c) -> {
+ if (frame == null) {
+ Slog.e(TAG, "Received null enrollment frame for face enroll client.");
+ return;
+ }
+ c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
+ }, null);
+ }
+
+ @Override
+ public void onError(byte error, int vendorCode) {
+ onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+ }
+
+ /**
+ * Handle error messages from the HAL.
+ */
+ public void onError(int error, int vendorCode) {
+ handleResponse(ErrorConsumer.class, (c) -> {
+ c.onError(error, vendorCode);
+ if (error == Error.HW_UNAVAILABLE) {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
+ }, null);
+ }
+
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ BaseClientMonitor client = mScheduler.getCurrentClient();
+ final int currentUserId;
+ if (client == null) {
+ return;
+ } else {
+ currentUserId = client.getTargetUserId();
+ }
+ final CharSequence name = FaceUtils.getInstance(mSensorId)
+ .getUniqueName(mContext, currentUserId);
+ final Face face = new Face(name, enrollmentId, mSensorId);
+
+ handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ final Face face = new Face("" /* name */, enrollmentId, mSensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+ true /* authenticated */, byteList), null);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
+ false /* authenticated */, null /* hat */), null);
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ }
+
+ @Override
+ public void onLockoutCleared() {
+ handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
+ (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
+ mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+ }
+
+ @Override
+ public void onInteractionDetected() {
+ handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+ }
+
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; ++i) {
+ final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
+ final int finalI = i;
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
+ enrollmentIds.length - finalI - 1), null);
+ }
+ } else {
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+ null /* identifier */, 0 /* remaining */), null);
+ }
+ }
+
+ @Override
+ public void onFeaturesRetrieved(byte[] features) {
+ handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
+ features), null);
+ }
+
+ @Override
+ public void onFeatureSet(byte feature) {
+ handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+ }
+
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
+ final int finalI = i;
+ handleResponse(RemovalConsumer.class,
+ (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
+ null);
+ }
+ } else {
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+ 0 /* remaining */), null);
+ }
+ }
+
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+ handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
+ authenticatorId), null);
+ }
+
+ @Override
+ public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+ handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+ newAuthenticatorId), null);
+ }
+
+ /**
+ * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
+ */
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
+ null);
+ }
+
+ /**
+ * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL).
+ */
+ public void onLockoutChanged(long duration) {
+ mScheduler.getHandler().post(() -> {
+ @LockoutTracker.LockoutMode final int lockoutMode;
+ if (duration == 0) {
+ lockoutMode = LockoutTracker.LOCKOUT_NONE;
+ } else if (duration == -1 || duration == Long.MAX_VALUE) {
+ lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
+ } else {
+ lockoutMode = LockoutTracker.LOCKOUT_TIMED;
+ }
+
+ mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+
+ if (duration == 0) {
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
+ }
+ });
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> actionIfClassMatchesClient,
+ @Nullable Consumer<BaseClientMonitor> alternateAction) {
+ mScheduler.getHandler().post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (className.isInstance(client)) {
+ actionIfClassMatchesClient.accept((T) client);
+ } else {
+ Slog.d(TAG, "Current client is not an instance of " + className.getName());
+ if (alternateAction != null) {
+ alternateAction.accept(client);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSessionClosed() {
+ mScheduler.getHandler().post(mScheduler::onUserStopped);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index 29eee6b5bb06..858bb864d4db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -16,10 +16,14 @@
package com.android.server.biometrics.sensors.face.aidl;
-import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback;
-
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
/**
* A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -31,14 +35,22 @@ public class AidlSession {
@NonNull
private final ISession mSession;
private final int mUserId;
- @NonNull private final HalSessionCallback mHalSessionCallback;
+ @NonNull private final AidlResponseHandler mAidlResponseHandler;
public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
- HalSessionCallback halSessionCallback) {
+ AidlResponseHandler aidlResponseHandler) {
mHalInterfaceVersion = halInterfaceVersion;
mSession = session;
mUserId = userId;
- mHalSessionCallback = halSessionCallback;
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
+ AidlResponseHandler aidlResponseHandler) {
+ mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+ mHalInterfaceVersion = 0;
+ mUserId = userId;
+ mAidlResponseHandler = aidlResponseHandler;
}
/** The underlying {@link ISession}. */
@@ -52,8 +64,8 @@ public class AidlSession {
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- HalSessionCallback getHalSessionCallback() {
- return mHalSessionCallback;
+ AidlResponseHandler getHalSessionCallback() {
+ return mAidlResponseHandler;
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 35fc43ae5f74..470dc4b7172c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -46,8 +46,8 @@ import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.face.UsageStats;
@@ -57,7 +57,8 @@ import java.util.function.Supplier;
/**
* Face-specific authentication client for the {@link IFace} AIDL HAL interface.
*/
-class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
+public class FaceAuthenticationClient
+ extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>
implements LockoutConsumer {
private static final String TAG = "FaceAuthenticationClient";
@@ -74,11 +75,11 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
@Nullable
private ICancellationSignal mCancellationSignal;
@Nullable
- private SensorPrivacyManager mSensorPrivacyManager;
+ private final SensorPrivacyManager mSensorPrivacyManager;
@FaceManager.FaceAcquired
private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
- FaceAuthenticationClient(@NonNull Context context,
+ public FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, long operationId,
@@ -86,7 +87,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
- @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+ @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
@Authenticators.Types int sensorStrength) {
this(context, lazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, requireConfirmation, logger, biometricContext,
@@ -103,12 +104,12 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
boolean requireConfirmation,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
- @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+ @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
SensorPrivacyManager sensorPrivacyManager,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
- isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */,
+ isStrongBiometric, null /* taskStackListener */, lockoutTracker,
allowBackgroundAuthentication, false /* shouldVibrate */,
biometricStrength);
setRequestId(requestId);
@@ -263,8 +264,13 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
mLastAcquire = acquireInfo;
final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
- PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+
+ //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL
+ if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser(
+ getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) {
+ PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
+ pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index f55cf0549382..dbed1f7a8f9d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -85,7 +85,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
}
};
- FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
index 165c3a241043..e404bd2be31e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java
@@ -36,7 +36,7 @@ import java.util.function.Supplier;
public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
private static final String TAG = "FaceGenerateChallengeClient";
- FaceGenerateChallengeClient(@NonNull Context context,
+ public FaceGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId, @NonNull BiometricLogger logger,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index ef3b345402bf..c15049b48bb2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
@@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
import java.util.HashMap;
import java.util.Map;
@@ -46,14 +48,16 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen
private static final String TAG = "FaceGetFeatureClient";
private final int mUserId;
+ private final int mFeature;
- FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext, int feature) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
logger, biometricContext);
mUserId = userId;
+ mFeature = feature;
}
@Override
@@ -70,7 +74,11 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getSession().getFeatures();
+ ISession session = getFreshDaemon().getSession();
+ if (session instanceof AidlToHidlAdapter) {
+ ((AidlToHidlAdapter) session).setFeature(mFeature);
+ }
+ session.getFeatures();
} catch (RemoteException e) {
Slog.e(TAG, "Unable to getFeature", e);
mCallback.onClientFinished(this, false /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index f09d192966f1..e75c6aba1489 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -38,9 +38,9 @@ import java.util.function.Supplier;
/**
* Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.
*/
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
+public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> {
- FaceInternalCleanupClient(@NonNull Context context,
+ public FaceInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,
int sensorId, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index cc3118cc3433..dd9c6d50ae9e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -493,7 +493,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
+ mUsageStats, null /* lockoutTracker */,
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -619,7 +619,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,
mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
- mBiometricContext);
+ mBiometricContext, feature);
scheduleForSensor(sensorId, client);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
index 0512017394af..079388822def 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java
@@ -36,12 +36,12 @@ import java.util.function.Supplier;
/**
* Face-specific removal client for the {@link IFace} AIDL HAL interface.
*/
-class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
+public class FaceRemovalClient extends RemovalClient<Face, AidlSession> {
private static final String TAG = "FaceRemovalClient";
final int[] mBiometricIds;
- FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
int[] biometricIds, int userId, @NonNull String owner,
@NonNull BiometricUtils<Face> utils, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 1a12fcdf5010..77b5592c5064 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -48,14 +47,14 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem
private static final String TAG = "FaceResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutCache;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
- FaceResetLockoutClient(@NonNull Context context,
+ public FaceResetLockoutClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+ @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,7 +106,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem
* be used instead.
*/
static void resetLocalLockoutStateToNone(int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@Authenticators.Types int biometricStrength, long requestId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
index 8838345de4d6..0d6143a7d0f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java
@@ -38,7 +38,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession
private final long mChallenge;
- FaceRevokeChallengeClient(@NonNull Context context,
+ public FaceRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 6c143872ff8c..f6da8726564f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -46,7 +46,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implemen
private final boolean mEnabled;
private final HardwareAuthToken mHardwareAuthToken;
- FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 2ad41c2a7a02..54e66eb4cca4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -23,16 +23,10 @@ import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.AuthenticationFrame;
-import android.hardware.biometrics.face.EnrollmentFrame;
-import android.hardware.biometrics.face.Error;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
-import android.hardware.biometrics.face.ISessionCallback;
-import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -44,29 +38,22 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@@ -91,397 +78,6 @@ public class Sensor {
@NonNull private final Supplier<AidlSession> mLazySession;
@Nullable AidlSession mCurrentSession;
- @VisibleForTesting
- public static class HalSessionCallback extends ISessionCallback.Stub {
- /**
- * Interface to sends results to the HalSessionCallback's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final Handler mHandler;
- @NonNull
- private final String mTag;
- @NonNull
- private final UserAwareBiometricScheduler mScheduler;
- private final int mSensorId;
- private final int mUserId;
- @NonNull
- private final LockoutCache mLockoutCache;
- @NonNull
- private final LockoutResetDispatcher mLockoutResetDispatcher;
-
- @NonNull
- private AuthSessionCoordinator mAuthSessionCoordinator;
- @NonNull
- private final Callback mCallback;
-
- HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull Callback callback) {
- mContext = context;
- mHandler = handler;
- mTag = tag;
- mScheduler = scheduler;
- mSensorId = sensorId;
- mUserId = userId;
- mLockoutCache = lockoutTracker;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mAuthSessionCoordinator = authSessionCoordinator;
- mCallback = callback;
- }
-
- @Override
- public int getInterfaceVersion() {
- return this.VERSION;
- }
-
- @Override
- public String getInterfaceHash() {
- return this.HASH;
- }
-
- @Override
- public void onChallengeGenerated(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceGenerateChallengeClient)) {
- Slog.e(mTag, "onChallengeGenerated for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceGenerateChallengeClient generateChallengeClient =
- (FaceGenerateChallengeClient) client;
- generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onChallengeRevoked(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceRevokeChallengeClient)) {
- Slog.e(mTag, "onChallengeRevoked for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceRevokeChallengeClient revokeChallengeClient =
- (FaceRevokeChallengeClient) client;
- revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onAuthenticationFrame(AuthenticationFrame frame) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceAuthenticationClient)) {
- Slog.e(mTag, "onAuthenticationFrame for incompatible client: "
- + Utils.getClientName(client));
- return;
-
- }
- if (frame == null) {
- Slog.e(mTag, "Received null authentication frame for client: "
- + Utils.getClientName(client));
- return;
- }
- ((FaceAuthenticationClient) client).onAuthenticationFrame(
- AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
- });
- }
-
- @Override
- public void onEnrollmentFrame(EnrollmentFrame frame) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceEnrollClient)) {
- Slog.e(mTag, "onEnrollmentFrame for incompatible client: "
- + Utils.getClientName(client));
- return;
- }
- if (frame == null) {
- Slog.e(mTag, "Received null enrollment frame for client: "
- + Utils.getClientName(client));
- return;
- }
- ((FaceEnrollClient) client).onEnrollmentFrame(
- AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
- });
- }
-
- @Override
- public void onError(byte error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(mTag, "onError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(mTag, "onError for non-error consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
- if (error == Error.HW_UNAVAILABLE) {
- mCallback.onHardwareUnavailable();
- }
- });
- }
-
- @Override
- public void onEnrollmentProgress(int enrollmentId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceEnrollClient)) {
- Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FaceUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
- final Face face = new Face(name, enrollmentId, mSensorId);
-
- final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
- enrollClient.onEnrollResult(face, remaining);
- });
- }
-
- @Override
- public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Face face = new Face("" /* name */, enrollmentId, mSensorId);
- final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
- final ArrayList<Byte> byteList = new ArrayList<>();
- for (byte b : byteArray) {
- byteList.add(b);
- }
- authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList);
- });
- }
-
- @Override
- public void onAuthenticationFailed() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
- authenticationConsumer.onAuthenticated(face, false /* authenticated */,
- null /* hat */);
- });
- }
-
- @Override
- public void onLockoutTimed(long durationMillis) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutTimed(durationMillis);
- });
- }
-
- @Override
- public void onLockoutPermanent() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutPermanent();
- });
- }
-
- @Override
- public void onLockoutCleared() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceResetLockoutClient)) {
- Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
- // Given that onLockoutCleared() can happen at any time, and is not necessarily
- // coming from a specific client, set this to -1 to indicate it wasn't for a
- // specific request.
- FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
- } else {
- Slog.d(mTag, "onLockoutCleared after resetLockout");
- final FaceResetLockoutClient resetLockoutClient =
- (FaceResetLockoutClient) client;
- resetLockoutClient.onLockoutCleared();
- }
- });
- }
-
- @Override
- public void onInteractionDetected() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceDetectClient)) {
- Slog.e(mTag, "onInteractionDetected for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceDetectClient detectClient = (FaceDetectClient) client;
- detectClient.onInteractionDetected();
- });
- }
-
- @Override
- public void onEnrollmentsEnumerated(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer =
- (EnumerateConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; ++i) {
- final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
- enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1);
- }
- } else {
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */);
- }
- });
- }
-
- @Override
- public void onFeaturesRetrieved(byte[] features) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceGetFeatureClient)) {
- Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: "
- + Utils.getClientName(client));
- return;
- }
- final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client;
- faceGetFeatureClient.onFeatureGet(true /* success */, features);
- });
-
- }
-
- @Override
- public void onFeatureSet(byte feature) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceSetFeatureClient)) {
- Slog.e(mTag, "onFeatureSet for non-set consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client;
- faceSetFeatureClient.onFeatureSet(true /* success */);
- });
- }
-
- @Override
- public void onEnrollmentsRemoved(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(mTag, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
- removalConsumer.onRemoved(face, enrollmentIds.length - i - 1);
- }
- } else {
- removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */);
- }
- });
- }
-
- @Override
- public void onAuthenticatorIdRetrieved(long authenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceGetAuthenticatorIdClient)) {
- Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceGetAuthenticatorIdClient getAuthenticatorIdClient =
- (FaceGetAuthenticatorIdClient) client;
- getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
- });
- }
-
- @Override
- public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceInvalidationClient)) {
- Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client;
- invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
- });
- }
-
- @Override
- public void onSessionClosed() {
- mHandler.post(mScheduler::onUserStopped);
- }
- }
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@@ -511,9 +107,9 @@ public class Sensor {
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final HalSessionCallback resultController = new HalSessionCallback(mContext,
- mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
- lockoutResetDispatcher,
+ final AidlResponseHandler resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutCache, lockoutResetDispatcher,
biometricContext.getAuthSessionCoordinator(), () -> {
Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
mCurrentSession = null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
new file mode 100644
index 000000000000..eecf44b92918
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.hidl;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.face.EnrollmentStageConfig;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.common.NativeHandle;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+
+ private final String TAG = "AidlToHidlAdapter";
+ private static final int CHALLENGE_TIMEOUT_SEC = 600;
+ @DurationMillisLong
+ private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
+ @DurationMillisLong
+ private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
+ private static final int INVALID_VALUE = -1;
+ private final Clock mClock;
+ private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+ @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+ private long mGenerateChallengeCreatedAt = INVALID_VALUE;
+ private long mGenerateChallengeResult = INVALID_VALUE;
+ @NonNull private Supplier<IBiometricsFace> mSession;
+ private final int mUserId;
+ private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+ private final Context mContext;
+ private int mFeature = INVALID_VALUE;
+
+ public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+ int userId, AidlResponseHandler aidlResponseHandler) {
+ this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
+ }
+
+ AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+ AidlResponseHandler aidlResponseHandler, Clock clock) {
+ mSession = session;
+ mUserId = userId;
+ mContext = context;
+ mClock = clock;
+ setCallback(aidlResponseHandler);
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ mSession.get().setCallback(mHidlToAidlCallbackConverter);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGenerateChallengeCreatedAt != INVALID_VALUE
+ && mGenerateChallengeResult != INVALID_VALUE
+ && mClock.millis() - mGenerateChallengeCreatedAt
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, mClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = mClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
+ @Override
+ public void generateChallenge() throws RemoteException {
+ incrementChallengeCount();
+ if (isGeneratedChallengeCacheValid()) {
+ Slog.d(TAG, "Current challenge is cached and will be reused");
+ mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+ return;
+ }
+ mGenerateChallengeCreatedAt = mClock.millis();
+ mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+ mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult);
+ }
+
+ @Override
+ public void revokeChallenge(long challenge) throws RemoteException {
+ final boolean shouldRevoke = decrementChallengeCount() == 0;
+ if (!shouldRevoke) {
+ Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+ + mGeneratedChallengeCount);
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+ return;
+ }
+ mGenerateChallengeCreatedAt = INVALID_VALUE;
+ mGenerateChallengeResult = INVALID_VALUE;
+ mSession.get().revokeChallenge();
+ mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+ }
+
+ @Override
+ public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
+ //unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features,
+ NativeHandle previewSurface) throws RemoteException {
+ final ArrayList<Byte> token = new ArrayList<>();
+ final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat);
+ for (byte b : hardwareAuthTokenArray) {
+ token.add(b);
+ }
+ final ArrayList<Integer> disabledFeatures = new ArrayList<>();
+ for (byte b: features) {
+ disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b));
+ }
+ mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal authenticate(long operationId) throws RemoteException {
+ mSession.get().authenticate(operationId);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction() throws RemoteException {
+ mSession.get().authenticate(0);
+ return new Cancellation();
+ }
+
+ @Override
+ public void enumerateEnrollments() throws RemoteException {
+ mSession.get().enumerate();
+ }
+
+ @Override
+ public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+ mSession.get().remove(enrollmentIds[0]);
+ }
+
+ /**
+ * Needs to be called before getFeatures is invoked.
+ */
+ public void setFeature(int feature) {
+ mFeature = feature;
+ }
+
+ @Override
+ public void getFeatures() throws RemoteException {
+ final int faceId = getFaceId();
+ if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) {
+ return;
+ }
+
+ final OptionalBool result = mSession.get()
+ .getFeature(mFeature, faceId);
+
+ if (result.status == Status.OK && result.value) {
+ mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils
+ .convertFrameworkToAidlFeature(mFeature)});
+ } else if (result.status == Status.OK) {
+ mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{});
+ } else {
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+ }
+
+ mFeature = INVALID_VALUE;
+ }
+
+ @Override
+ public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+ throws RemoteException {
+ final int faceId = getFaceId();
+ if (faceId == INVALID_VALUE) {
+ return;
+ }
+ ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>();
+ for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) {
+ hardwareAuthTokenList.add(b);
+ }
+ final int result = mSession.get().setFeature(
+ AidlConversionUtils.convertAidlToFrameworkFeature(feature),
+ enabled, hardwareAuthTokenList, faceId);
+ if (result == Status.OK) {
+ mHidlToAidlCallbackConverter.onFeatureSet(feature);
+ } else {
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */);
+ }
+ }
+
+ private int getFaceId() {
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+ if (faces.isEmpty()) {
+ Slog.d(TAG, "No faces to get feature from.");
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+ return INVALID_VALUE;
+ }
+
+ return faces.get(0).getBiometricId();
+ }
+
+ @Override
+ public void getAuthenticatorId() throws RemoteException {
+ long authenticatorId = mSession.get().getAuthenticatorId().value;
+ mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId);
+ }
+
+ @Override
+ public void invalidateAuthenticatorId() throws RemoteException {
+ //unsupported in HIDL
+ }
+
+ @Override
+ public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+ ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+ for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+ hardwareAuthToken.add(b);
+ }
+ mSession.get().resetLockout(hardwareAuthToken);
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
+ NativeHandle previewSurface, OperationContext context) throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteractionWithContext(OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public void onContextChanged(OperationContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ //Unsupported in HIDL
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ /**
+ * Cancellation in HIDL occurs for the entire session, instead of a specific client.
+ */
+ private class Cancellation extends ICancellationSignal.Stub {
+
+ Cancellation() {}
+ @Override
+ public void cancel() throws RemoteException {
+ try {
+ mSession.get().cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1499317478aa..46ce0b62e6d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -61,6 +62,7 @@ import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -78,6 +80,8 @@ import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.LockoutHalImpl;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
import org.json.JSONArray;
import org.json.JSONException;
@@ -131,7 +135,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
private int mCurrentUserId = UserHandle.USER_NULL;
private final int mSensorId;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
+ private AidlSession mSession;
private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
@Override
@@ -361,6 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mLockoutTracker = new LockoutHalImpl();
mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
mScheduler, mLockoutTracker, lockoutResetDispatcher);
+ mLockoutResetDispatcher = lockoutResetDispatcher;
mHalResultController.setCallback(() -> {
mDaemon = null;
mCurrentUserId = UserHandle.USER_NULL;
@@ -420,6 +427,24 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
});
}
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ synchronized AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId,
+ new AidlResponseHandler(mContext, mScheduler, mSensorId,
+ mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+ new AuthSessionCoordinator(), () -> {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }));
+ }
+ }
+
private synchronized IBiometricsFace getDaemon() {
if (mTestHalEnabled) {
final TestHal testHal = new TestHal(mContext, mSensorId);
@@ -551,32 +576,63 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- incrementChallengeCount();
+ scheduleUpdateActiveUserWithoutHandler(userId);
- if (isGeneratedChallengeCacheValid()) {
- Slog.d(TAG, "Current challenge is cached and will be reused");
- mGeneratedChallengeCache.reuseResult(receiver);
- return;
+ if (Flags.deHidl()) {
+ scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+ } else {
+ scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
}
+ });
+ }
- scheduleUpdateActiveUserWithoutHandler(userId);
+ private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleGenerateChallenge onClientStarted, mismatched client."
+ + " Expecting: " + client + ", received: "
+ + clientMonitor);
+ }
+ }
+ });
+ }
- final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, sSystemClock.millis());
- mGeneratedChallengeCache = client;
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- if (client != clientMonitor) {
- Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client."
- + " Expecting: " + client + ", received: " + clientMonitor);
- }
+ private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ incrementChallengeCount();
+ if (isGeneratedChallengeCacheValid()) {
+ Slog.d(TAG, "Current challenge is cached and will be reused");
+ mGeneratedChallengeCache.reuseResult(receiver);
+ return;
+ }
+
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
+ opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, sSystemClock.millis());
+ mGeneratedChallengeCache = client;
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleGenerateChallenge onClientStarted, mismatched client."
+ + " Expecting: " + client + ", received: "
+ + clientMonitor);
}
- });
+ }
});
}
@@ -584,31 +640,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final boolean shouldRevoke = decrementChallengeCount() == 0;
- if (!shouldRevoke) {
- Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
- + mGeneratedChallengeCount);
- return;
+ if (Flags.deHidl()) {
+ scheduleRevokeChallengeAidl(userId, token, opPackageName);
+ } else {
+ scheduleRevokeChallengeHidl(userId, token, opPackageName);
+ }
+ });
+ }
+
+ private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient
+ client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient(
+ mContext, this::getSession, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext, 0L);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+ + client + ", received: " + clientMonitor);
+ }
}
+ });
+ }
- Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
- mGeneratedChallengeCache = null;
+ private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final boolean shouldRevoke = decrementChallengeCount() == 0;
+ if (!shouldRevoke) {
+ Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
+ + mGeneratedChallengeCount);
+ return;
+ }
- final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mLazyDaemon, token, userId, opPackageName, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (client != clientMonitor) {
- Slog.e(TAG, "scheduleRevokeChallenge, mismatched client."
- + "Expecting: " + client + ", received: " + clientMonitor);
- }
+ Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
+ mGeneratedChallengeCache = null;
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mLazyDaemon, token, userId, opPackageName, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (client != clientMonitor) {
+ Slog.e(TAG,
+ "scheduleRevokeChallenge, mismatched client." + "Expecting: "
+ + client + ", received: " + clientMonitor);
}
- });
+ }
});
}
@@ -620,7 +707,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
+ if (Flags.deHidl()) {
+ scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, disabledFeatures, previewSurface, id);
+ } else {
+ scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, disabledFeatures, previewSurface, id);
+ }
+ });
+ return id;
+ }
+
+ private void scheduleEnrollAidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+ @Nullable Surface previewSurface, long id) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId,
+ hardwareAuthToken, opPackageName, id,
+ FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
+ ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
+ false);
+
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
+ }
+ }
+ });
+ }
+
+ private void scheduleEnrollHidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
+ @NonNull String opPackageName, @NonNull int[] disabledFeatures,
+ @Nullable Surface previewSurface, long id) {
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
@@ -628,7 +770,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
-
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -650,8 +791,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
}
});
- });
- return id;
}
@Override
@@ -683,18 +822,46 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
- final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, requestId, receiver, operationId, restricted,
- options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, isStrongBiometric, mLockoutTracker,
- mUsageStats, allowBackgroundAuthentication,
- Utils.getCurrentStrength(mSensorId));
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ } else {
+ scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ }
});
}
+ private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+ int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient
+ client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient(
+ mContext, this::getSession, token, requestId, receiver, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector), mBiometricContext,
+ isStrongBiometric, mUsageStats, mLockoutTracker,
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
+ int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
+ mLazyDaemon, token, requestId, receiver, operationId, restricted, options,
+ cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector), mBiometricContext,
+ isStrongBiometric, mLockoutTracker, mUsageStats,
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter receiver,
@@ -719,13 +886,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
+ } else {
+ scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
+ }
});
}
@@ -736,17 +901,39 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
scheduleUpdateActiveUserWithoutHandler(userId);
// For IBiometricsFace@1.0, remove(0) means remove all enrollments
- final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId,
- opPackageName,
- FaceUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, userId, receiver, opPackageName, 0);
+ } else {
+ scheduleRemoveHidl(token, userId, receiver, opPackageName, 0);
+ }
});
}
+ private void scheduleRemoveAidl(@NonNull IBinder token, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId,
+ opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector), mBiometricContext,
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleRemoveHidl(@NonNull IBinder token, int userId,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
+ final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), faceId, userId,
+ opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
@@ -756,89 +943,180 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
scheduleUpdateActiveUserWithoutHandler(userId);
-
- final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, hardwareAuthToken);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleResetLockoutAidl(userId, hardwareAuthToken);
+ } else {
+ scheduleResetLockoutHidl(userId, hardwareAuthToken);
+ }
});
}
+ private void scheduleResetLockoutAidl(int userId,
+ @NonNull byte[] hardwareAuthToken) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, hardwareAuthToken, mLockoutTracker,
+ mLockoutResetDispatcher, mSensorProperties.sensorStrength);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleResetLockoutHidl(int userId,
+ @NonNull byte[] hardwareAuthToken) {
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
+ mLazyDaemon,
+ userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
boolean enabled, @NonNull byte[] hardwareAuthToken,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- final List<Face> faces = getEnrolledFaces(sensorId, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
- return;
+ scheduleUpdateActiveUserWithoutHandler(userId);
+ if (Flags.deHidl()) {
+ scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+ receiver, opPackageName);
+ } else {
+ scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
+ receiver, opPackageName);
}
+ });
+ }
- scheduleUpdateActiveUserWithoutHandler(userId);
+ private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+ int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ final List<Face> faces = getEnrolledFaces(sensorId, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
+ return;
+ }
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon,
+ token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature,
+ enabled, hardwareAuthToken, faceId);
+ mScheduler.scheduleClientMonitor(client);
+ }
- final int faceId = faces.get(0).getBiometricId();
- final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext),
- mBiometricContext,
- feature, enabled, hardwareAuthToken, faceId);
- mScheduler.scheduleClientMonitor(client);
- });
+ private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId,
+ int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
+ @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature, enabled, hardwareAuthToken);
+ mScheduler.scheduleClientMonitor(client);
}
+
@Override
public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
mHandler.post(() -> {
- final List<Face> faces = getEnrolledFaces(sensorId, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
- return;
+ scheduleUpdateActiveUserWithoutHandler(userId);
+
+ if (Flags.deHidl()) {
+ scheduleGetFeatureAidl(token, userId, feature, listener,
+ opPackageName);
+ } else {
+ scheduleGetFeatureHidl(sensorId, token, userId, feature, listener,
+ opPackageName);
}
+ });
+ }
- scheduleUpdateActiveUserWithoutHandler(userId);
+ private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
+ int feature, @Nullable ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName) {
+ final List<Face> faces = getEnrolledFaces(sensorId, userId);
+ if (faces.isEmpty()) {
+ Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
+ return;
+ }
- final int faceId = faces.get(0).getBiometricId();
- final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
- token, listener, userId, opPackageName, mSensorId,
- BiometricLogger.ofUnknown(mContext), mBiometricContext,
- feature, faceId);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(
- @NonNull BaseClientMonitor clientMonitor, boolean success) {
- if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
- final int settingsValue = client.getValue() ? 1 : 0;
- Slog.d(TAG, "Updating attention value for user: " + userId
- + " to value: " + settingsValue);
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
- settingsValue, userId);
- }
+ final int faceId = faces.get(0).getBiometricId();
+ final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
+ token, listener, userId, opPackageName, mSensorId,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success
+ && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
+ final int settingsValue = client.getValue() ? 1 : 0;
+ Slog.d(TAG,
+ "Updating attention value for user: " + userId + " to value: "
+ + settingsValue);
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue,
+ userId);
}
- });
+ }
});
}
+ private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId,
+ int feature, @Nullable ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient(
+ mContext, this::getSession, token, listener, userId, opPackageName,
+ mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
+ feature);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
private void scheduleInternalCleanup(int userId,
@Nullable ClientMonitorCallback callback) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
-
- final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext,
- FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
- mBiometricStateCallback));
+ if (Flags.deHidl()) {
+ scheduleInternalCleanupAidl(userId, callback);
+ } else {
+ scheduleInternalCleanupHidl(userId, callback);
+ }
});
}
+ private void scheduleInternalCleanupHidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
+ mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client,
+ new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+ }
+
+ private void scheduleInternalCleanupAidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient
+ client =
+ new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
+ mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client,
+ new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
+ }
+
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback) {
@@ -970,6 +1248,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
if (success) {
+ if (mCurrentUserId != targetUserId) {
+ // Create new session with updated user ID
+ mSession = null;
+ }
mCurrentUserId = targetUserId;
} else {
Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
new file mode 100644
index 000000000000..36a9790d2d4b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.hidl;
+
+import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub {
+
+ private final AidlResponseHandler mAidlResponseHandler;
+
+ public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) {
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ @Override
+ public void onEnrollResult(
+ long deviceId, int faceId, int userId, int remaining) {
+ mAidlResponseHandler.onEnrollmentProgress(faceId, remaining);
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int faceId, int userId,
+ ArrayList<Byte> token) {
+ final boolean authenticated = faceId != 0;
+ byte[] hardwareAuthToken = new byte[token.size()];
+
+ for (int i = 0; i < token.size(); i++) {
+ hardwareAuthToken[i] = token.get(i);
+ }
+
+ if (authenticated) {
+ mAidlResponseHandler.onAuthenticationSucceeded(faceId,
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+ } else {
+ mAidlResponseHandler.onAuthenticationFailed();
+ }
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int userId, int acquiredInfo,
+ int vendorCode) {
+ mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onError(long deviceId, int userId, int error, int vendorCode) {
+ mAidlResponseHandler.onError(error, vendorCode);
+ }
+
+ @Override
+ public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
+ int[] enrollmentIds = new int[removed.size()];
+ for (int i = 0; i < removed.size(); i++) {
+ enrollmentIds[i] = removed.get(i);
+ }
+ mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds);
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
+ int[] enrollmentIds = new int[faceIds.size()];
+ for (int i = 0; i < faceIds.size(); i++) {
+ enrollmentIds[i] = faceIds.get(i);
+ }
+ mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds);
+ }
+
+ @Override
+ public void onLockoutChanged(long duration) {
+ mAidlResponseHandler.onLockoutChanged(duration);
+ }
+
+ void onChallengeGenerated(long challenge) {
+ mAidlResponseHandler.onChallengeGenerated(challenge);
+ }
+
+ void onChallengeRevoked(long challenge) {
+ mAidlResponseHandler.onChallengeRevoked(challenge);
+ }
+
+ void onFeatureGet(byte[] features) {
+ mAidlResponseHandler.onFeaturesRetrieved(features);
+ }
+
+ void onFeatureSet(byte feature) {
+ mAidlResponseHandler.onFeatureSet(feature);
+ }
+
+ void onAuthenticatorIdRetrieved(long authenticatorId) {
+ mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
new file mode 100644
index 000000000000..4a019436cf6f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.Error;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.EnumerateConsumer;
+import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+
+/**
+ * Response handler for the {@link ISessionCallback} HAL AIDL interface.
+ */
+public class AidlResponseHandler extends ISessionCallback.Stub {
+
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface HardwareUnavailableCallback {
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
+ private static final String TAG = "AidlResponseHandler";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final BiometricScheduler mScheduler;
+ private final int mSensorId;
+ private final int mUserId;
+ @NonNull
+ private final LockoutCache mLockoutCache;
+ @NonNull
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ @NonNull
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ mContext = context;
+ mScheduler = scheduler;
+ mSensorId = sensorId;
+ mUserId = userId;
+ mLockoutCache = lockoutTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+
+ @Override
+ public void onChallengeGenerated(long challenge) {
+ handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
+ mSensorId, mUserId, challenge), null);
+ }
+
+ @Override
+ public void onChallengeRevoked(long challenge) {
+ handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
+ challenge), null);
+ }
+
+ /**
+ * Handles acquired messages sent by the HAL (specifically for HIDL HAL).
+ */
+ public void onAcquired(int acquiredInfo, int vendorCode) {
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
+ null);
+ }
+
+ @Override
+ public void onAcquired(byte info, int vendorCode) {
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
+ AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+ }
+
+ /**
+ * Handle error messages from the HAL.
+ */
+ public void onError(int error, int vendorCode) {
+ handleResponse(ErrorConsumer.class, (c) -> {
+ c.onError(error, vendorCode);
+ if (error == Error.HW_UNAVAILABLE) {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
+ }, null);
+ }
+
+ @Override
+ public void onError(byte error, int vendorCode) {
+ onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
+ }
+
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ BaseClientMonitor client = mScheduler.getCurrentClient();
+ final int currentUserId;
+ if (client == null) {
+ return;
+ } else {
+ currentUserId = client.getTargetUserId();
+ }
+ final CharSequence name = FingerprintUtils.getInstance(mSensorId)
+ .getUniqueName(mContext, currentUserId);
+ final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
+ enrollmentId, mSensorId);
+ handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
+ remaining), null);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+ true /* authenticated */, byteList), (c) -> onInteractionDetected());
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
+ handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp,
+ false /* authenticated */, null /* hardwareAuthToken */),
+ (c) -> onInteractionDetected());
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
+ null);
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ }
+
+ @Override
+ public void onLockoutCleared() {
+ handleResponse(FingerprintResetLockoutClient.class,
+ FingerprintResetLockoutClient::onLockoutCleared,
+ (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
+ mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+ mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
+ -1 /* requestId */));
+ }
+
+ @Override
+ public void onInteractionDetected() {
+ handleResponse(FingerprintDetectClient.class,
+ FingerprintDetectClient::onInteractionDetected, null);
+ }
+
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ int finalI = i;
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
+ enrollmentIds.length - finalI - 1), null);
+ }
+ } else {
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
+ 0), null);
+ }
+ }
+
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+ if (enrollmentIds.length > 0) {
+ for (int i = 0; i < enrollmentIds.length; i++) {
+ final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
+ int finalI = i;
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
+ enrollmentIds.length - finalI - 1), null);
+ }
+ } else {
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
+ null);
+ }
+ }
+
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+ handleResponse(FingerprintGetAuthenticatorIdClient.class,
+ (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+ }
+
+ @Override
+ public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
+ handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
+ newAuthenticatorId), null);
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action,
+ @Nullable Consumer<BaseClientMonitor> alternateAction) {
+ mScheduler.getHandler().post(() -> {
+ final BaseClientMonitor client = mScheduler.getCurrentClient();
+ if (className.isInstance(client)) {
+ action.accept((T) client);
+ } else {
+ Slog.e(TAG, "Client monitor is not an instance of " + className.getName());
+ if (alternateAction != null) {
+ alternateAction.accept(client);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onSessionClosed() {
+ mScheduler.getHandler().post(mScheduler::onUserStopped);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 55861bb4cc6f..299a310caee9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -16,10 +16,13 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback;
-
import android.annotation.NonNull;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+
+import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+
+import java.util.function.Supplier;
/**
* A holder for an AIDL {@link ISession} with additional metadata about the current user
@@ -30,14 +33,22 @@ public class AidlSession {
private final int mHalInterfaceVersion;
@NonNull private final ISession mSession;
private final int mUserId;
- @NonNull private final HalSessionCallback mHalSessionCallback;
+ @NonNull private final AidlResponseHandler mAidlResponseHandler;
public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId,
- HalSessionCallback halSessionCallback) {
+ AidlResponseHandler aidlResponseHandler) {
mHalInterfaceVersion = halInterfaceVersion;
mSession = session;
mUserId = userId;
- mHalSessionCallback = halSessionCallback;
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
+ int userId, AidlResponseHandler aidlResponseHandler) {
+ mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+ mHalInterfaceVersion = 0;
+ mUserId = userId;
+ mAidlResponseHandler = aidlResponseHandler;
}
/** The underlying {@link ISession}. */
@@ -51,8 +62,8 @@ public class AidlSession {
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- HalSessionCallback getHalSessionCallback() {
- return mHalSessionCallback;
+ AidlResponseHandler getHalSessionCallback() {
+ return mAidlResponseHandler;
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 54d1faa39be0..337c3c299e54 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -51,8 +52,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
@@ -66,7 +67,7 @@ import java.util.function.Supplier;
* Fingerprint-specific authentication client supporting the {@link
* android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintAuthenticationClient
+public class FingerprintAuthenticationClient
extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
@@ -93,7 +94,7 @@ class FingerprintAuthenticationClient
private Runnable mAuthSuccessRunnable;
private final Clock mClock;
- FingerprintAuthenticationClient(
+ public FingerprintAuthenticationClient(
@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token,
@@ -108,14 +109,14 @@ class FingerprintAuthenticationClient
@NonNull BiometricContext biometricContext,
boolean isStrongBiometric,
@Nullable TaskStackListener taskStackListener,
- @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
@Authenticators.Types int biometricStrength,
- @NonNull Clock clock) {
+ @NonNull Clock clock,
+ @Nullable LockoutTracker lockoutTracker) {
super(
context,
lazyDaemon,
@@ -130,7 +131,7 @@ class FingerprintAuthenticationClient
biometricContext,
isStrongBiometric,
taskStackListener,
- null /* lockoutCache */,
+ lockoutTracker,
allowBackgroundAuthentication,
false /* shouldVibrate */,
biometricStrength);
@@ -211,6 +212,7 @@ class FingerprintAuthenticationClient
boolean authenticated,
ArrayList<Byte> token) {
super.onAuthenticated(identifier, authenticated, token);
+ handleLockout(authenticated);
if (authenticated) {
mState = STATE_STOPPED;
mSensorOverlays.hide(getSensorId());
@@ -219,6 +221,32 @@ class FingerprintAuthenticationClient
}
}
+ private void handleLockout(boolean authenticated) {
+ if (getLockoutTracker() == null) {
+ Slog.d(TAG, "Lockout is implemented by the HAL");
+ return;
+ }
+ if (authenticated) {
+ getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+ getTargetUserId());
+ } else {
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(getTargetUserId());
+ if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
+ Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
+ final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
+ ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
+ // controlled by the HAL, the framework must stop the sensor before finishing the
+ // client.
+ mSensorOverlays.hide(getSensorId());
+ onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
+ cancel();
+ }
+ }
+ }
+
@Override
public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
// For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 4502e5d0c4b6..e2413ee1c016 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -43,7 +43,8 @@ import java.util.function.Supplier;
* Performs fingerprint detection without exposing any matching information (e.g. accept/reject
* have the same haptic, lockout counter is not increased).
*/
-class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer {
+public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
+ implements DetectionConsumer {
private static final String TAG = "FingerprintDetectClient";
@@ -52,7 +53,8 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements
@NonNull private final SensorOverlays mSensorOverlays;
@Nullable private ICancellationSignal mCancellationSignal;
- FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+ public FingerprintDetectClient(@NonNull Context context,
+ @NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener,
@NonNull FingerprintAuthenticateOptions options,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 46ff6b4fab1a..06550d8b4fce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -49,14 +49,13 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.function.Supplier;
-class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
+public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,
PowerPressHandler {
private static final String TAG = "FingerprintEnrollClient";
@@ -72,12 +71,16 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
private static boolean shouldVibrateFor(Context context,
FingerprintSensorPropertiesInternal sensorProps) {
- final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
- final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
- return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+ if (sensorProps != null) {
+ final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+ final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
+ return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled;
+ } else {
+ return true;
+ }
}
- FingerprintEnrollClient(@NonNull Context context,
+ public FingerprintEnrollClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@@ -89,8 +92,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger,
- biometricContext);
+ 0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps),
+ logger, biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
@@ -136,7 +139,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
// For UDFPS, notify SysUI that the illumination can be turned off.
// See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
- if (mSensorProps.isAnyUdfpsType()) {
+ if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) {
if (acquiredGood && mShouldVibrate) {
vibrateSuccess();
}
@@ -162,8 +165,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
@Override
protected boolean hasReachedEnrollmentLimit() {
- return FingerprintUtils.getInstance(getSensorId())
- .getBiometricsForUser(getContext(), getTargetUserId()).size()
+ return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size()
>= mMaxTemplatesPerUser;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index ddae8bedf7c1..ce693ff58e19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -33,10 +33,10 @@ import java.util.function.Supplier;
/**
* Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
*/
-class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
+public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {
private static final String TAG = "FingerprintGenerateChallengeClient";
- FingerprintGenerateChallengeClient(@NonNull Context context,
+ public FingerprintGenerateChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
@NonNull IBinder token,
@NonNull ClientMonitorCallbackConverter listener,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index ff9127f516af..5edc2ca080ad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -39,9 +39,10 @@ import java.util.function.Supplier;
* Fingerprint-specific internal cleanup client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> {
+public class FingerprintInternalCleanupClient
+ extends InternalCleanupClient<Fingerprint, AidlSession> {
- FingerprintInternalCleanupClient(@NonNull Context context,
+ public FingerprintInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index e42b66472cbe..9985b06833c9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -428,8 +428,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ opPackageName, FingerprintUtils.getInstance(sensorId),
+ sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
@@ -496,12 +496,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
+ mTaskStackListener,
mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication,
mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
- SystemClock.elapsedRealtimeClock());
+ SystemClock.elapsedRealtimeClock(),
+ null /* lockoutTracker */);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
index d559bb1d72f1..4f08f6fbde54 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java
@@ -37,12 +37,12 @@ import java.util.function.Supplier;
* Fingerprint-specific removal client supporting the
* {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.
*/
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
+public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {
private static final String TAG = "FingerprintRemovalClient";
private final int[] mBiometricIds;
- FingerprintRemovalClient(@NonNull Context context,
+ public FingerprintRemovalClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
@Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,
@NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 7a620349075c..ec225a60d54b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -43,19 +42,20 @@ import java.util.function.Supplier;
* Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
* cleared.
*/
-class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer {
+public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession>
+ implements ErrorConsumer {
private static final String TAG = "FingerprintResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutCache;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
- FingerprintResetLockoutClient(@NonNull Context context,
+ public FingerprintResetLockoutClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+ @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
@@ -107,10 +107,11 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem
* be used instead.
*/
static void resetLocalLockoutStateToNone(int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@Authenticators.Types int biometricStrength, long requestId) {
+ lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);
lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);
authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
index afa62e2e3173..23d8e1e63f63 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -32,13 +32,13 @@ import java.util.function.Supplier;
/**
* Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
*/
-class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
+public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> {
- private static final String TAG = "FingerpirntRevokeChallengeClient";
+ private static final String TAG = "FingerprintRevokeChallengeClient";
private final long mChallenge;
- FingerprintRevokeChallengeClient(@NonNull Context context,
+ public FingerprintRevokeChallengeClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,
int userId, @NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
@@ -57,7 +57,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession
}
}
- void onChallengeRevoked(int sensorId, int userId, long challenge) {
+ void onChallengeRevoked(long challenge) {
final boolean success = challenge == mChallenge;
mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 56b85ceb8e6b..893cb8f9b4fc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -23,13 +23,9 @@ import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.Error;
import android.hardware.biometrics.fingerprint.ISession;
-import android.hardware.biometrics.fingerprint.ISessionCallback;
-import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.keymaster.HardwareAuthToken;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -39,34 +35,25 @@ import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@@ -93,348 +80,6 @@ public class Sensor {
@Nullable AidlSession mCurrentSession;
@NonNull private final Supplier<AidlSession> mLazySession;
- @VisibleForTesting
- public static class HalSessionCallback extends ISessionCallback.Stub {
-
- /**
- * Interface to sends results to the HalSessionCallback's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final Handler mHandler;
- @NonNull
- private final String mTag;
- @NonNull
- private final UserAwareBiometricScheduler mScheduler;
- private final int mSensorId;
- private final int mUserId;
- @NonNull
- private final LockoutCache mLockoutCache;
- @NonNull
- private final LockoutResetDispatcher mLockoutResetDispatcher;
- @NonNull
- private AuthSessionCoordinator mAuthSessionCoordinator;
- @NonNull
- private final Callback mCallback;
-
- HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull Callback callback) {
- mContext = context;
- mHandler = handler;
- mTag = tag;
- mScheduler = scheduler;
- mSensorId = sensorId;
- mUserId = userId;
- mLockoutCache = lockoutTracker;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mAuthSessionCoordinator = authSessionCoordinator;
- mCallback = callback;
- }
-
- @Override
- public int getInterfaceVersion() {
- return this.VERSION;
- }
-
- @Override
- public String getInterfaceHash() {
- return this.HASH;
- }
-
- @Override
- public void onChallengeGenerated(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGenerateChallengeClient)) {
- Slog.e(mTag, "onChallengeGenerated for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGenerateChallengeClient generateChallengeClient =
- (FingerprintGenerateChallengeClient) client;
- generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onChallengeRevoked(long challenge) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintRevokeChallengeClient)) {
- Slog.e(mTag, "onChallengeRevoked for wrong client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintRevokeChallengeClient revokeChallengeClient =
- (FingerprintRevokeChallengeClient) client;
- revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge);
- });
- }
-
- @Override
- public void onAcquired(byte info, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(mTag, "onAcquired for non-acquisition client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info),
- vendorCode);
- });
- }
-
- @Override
- public void onError(byte error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(mTag, "onError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(mTag, "onError for non-error consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode);
-
- if (error == Error.HW_UNAVAILABLE) {
- mCallback.onHardwareUnavailable();
- }
- });
- }
-
- @Override
- public void onEnrollmentProgress(int enrollmentId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintEnrollClient)) {
- Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FingerprintUtils.getInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
- final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId);
-
- final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
- enrollClient.onEnrollResult(fingerprint, remaining);
- });
- }
-
- @Override
- public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
- final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
- final ArrayList<Byte> byteList = new ArrayList<>();
- for (byte b : byteArray) {
- byteList.add(b);
- }
-
- authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
- });
- }
-
- @Override
- public void onAuthenticationFailed() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId);
- authenticationConsumer
- .onAuthenticated(fp, false /* authenticated */, null /* hat */);
- });
- }
-
- @Override
- public void onLockoutTimed(long durationMillis) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutTimed(durationMillis);
- });
- }
-
- @Override
- public void onLockoutPermanent() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof LockoutConsumer)) {
- Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
- lockoutConsumer.onLockoutPermanent();
- });
- }
-
- @Override
- public void onLockoutCleared() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintResetLockoutClient)) {
- Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL");
- // Given that onLockoutCleared() can happen at any time, and is not necessarily
- // coming from a specific client, set this to -1 to indicate it wasn't for a
- // specific request.
- FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */);
- } else {
- Slog.d(mTag, "onLockoutCleared after resetLockout");
- final FingerprintResetLockoutClient resetLockoutClient =
- (FingerprintResetLockoutClient) client;
- resetLockoutClient.onLockoutCleared();
- }
- });
- }
-
- @Override
- public void onInteractionDetected() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintDetectClient)) {
- Slog.e(mTag, "onInteractionDetected for non-detect client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintDetectClient fingerprintDetectClient =
- (FingerprintDetectClient) client;
- fingerprintDetectClient.onInteractionDetected();
- });
- }
-
- @Override
- public void onEnrollmentsEnumerated(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer =
- (EnumerateConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1);
- }
- } else {
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onEnrollmentsRemoved(int[] enrollmentIds) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(mTag, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- if (enrollmentIds.length > 0) {
- for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1);
- }
- } else {
- removalConsumer.onRemoved(null, 0);
- }
- });
- }
-
- @Override
- public void onAuthenticatorIdRetrieved(long authenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintGetAuthenticatorIdClient)) {
- Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient =
- (FingerprintGetAuthenticatorIdClient) client;
- getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId);
- });
- }
-
- @Override
- public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintInvalidationClient)) {
- Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final FingerprintInvalidationClient invalidationClient =
- (FingerprintInvalidationClient) client;
- invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId);
- });
- }
-
- @Override
- public void onSessionClosed() {
- mHandler.post(mScheduler::onUserStopped);
- }
- }
-
Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -466,9 +111,9 @@ public class Sensor {
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final HalSessionCallback resultController = new HalSessionCallback(mContext,
- mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache,
- lockoutResetDispatcher,
+ final AidlResponseHandler resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutCache, lockoutResetDispatcher,
biometricContext.getAuthSessionCoordinator(), () -> {
Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
mCurrentSession = null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
new file mode 100644
index 000000000000..b48d232e65af
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.function.Supplier;
+
+/**
+ * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ */
+public class AidlToHidlAdapter implements ISession {
+ private final String TAG = "AidlToHidlAdapter";
+ @VisibleForTesting
+ static final int ENROLL_TIMEOUT_SEC = 60;
+ @NonNull
+ private final Supplier<IBiometricsFingerprint> mSession;
+ private final int mUserId;
+ private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
+
+ public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+ AidlResponseHandler aidlResponseHandler) {
+ mSession = session;
+ mUserId = userId;
+ setCallback(aidlResponseHandler);
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ mSession.get().setNotify(mHidlToAidlCallbackConverter);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public void generateChallenge() throws RemoteException {
+ long challenge = mSession.get().preEnroll();
+ mHidlToAidlCallbackConverter.onChallengeGenerated(challenge);
+ }
+
+ @Override
+ public void revokeChallenge(long challenge) throws RemoteException {
+ mSession.get().postEnroll();
+ mHidlToAidlCallbackConverter.onChallengeRevoked(0L);
+ }
+
+ @Override
+ public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException {
+ mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId,
+ ENROLL_TIMEOUT_SEC);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal authenticate(long operationId) throws RemoteException {
+ mSession.get().authenticate(operationId, mUserId);
+ return new Cancellation();
+ }
+
+ @Override
+ public ICancellationSignal detectInteraction() throws RemoteException {
+ mSession.get().authenticate(0, mUserId);
+ return new Cancellation();
+ }
+
+ @Override
+ public void enumerateEnrollments() throws RemoteException {
+ mSession.get().enumerate();
+ }
+
+ @Override
+ public void removeEnrollments(int[] enrollmentIds) throws RemoteException {
+ if (enrollmentIds.length > 1) {
+ mSession.get().remove(mUserId, 0);
+ } else {
+ mSession.get().remove(mUserId, enrollmentIds[0]);
+ }
+ }
+
+ @Override
+ public void onPointerDown(int pointerId, int x, int y, float minor, float major)
+ throws RemoteException {
+ UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major);
+ }
+
+ @Override
+ public void onPointerUp(int pointerId) throws RemoteException {
+ UdfpsHelper.onFingerUp(mSession.get());
+ }
+
+ @Override
+ public void getAuthenticatorId() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void invalidateAuthenticatorId() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void resetLockout(HardwareAuthToken hat) throws RemoteException {
+ mHidlToAidlCallbackConverter.onResetLockout();
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onUiReady() throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal detectInteractionWithContext(OperationContext context)
+ throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ @Override
+ public void onPointerDownWithContext(PointerContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onPointerUpWithContext(PointerContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onContextChanged(OperationContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
+ //Unsupported in HIDL
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ //Unsupported in HIDL
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ //Unsupported in HIDL
+ return null;
+ }
+
+ private class Cancellation extends ICancellationSignal.Stub {
+
+ Cancellation() {}
+ @Override
+ public void cancel() throws RemoteException {
+ try {
+ mSession.get().cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public String getInterfaceHash() throws RemoteException {
+ return null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index a655f3601a07..8bfa560b8031 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -64,6 +65,7 @@ import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
@@ -74,6 +76,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
+import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -82,6 +85,8 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
import org.json.JSONArray;
import org.json.JSONException;
@@ -132,6 +137,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
private final boolean mIsUdfps;
private final int mSensorId;
private final boolean mIsPowerbuttonFps;
+ private AidlSession mSession;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -413,6 +419,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
});
}
+ synchronized AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(this::getDaemon,
+ mCurrentUserId, new AidlResponseHandler(mContext,
+ mScheduler, mSensorId, mCurrentUserId, new LockoutCache(),
+ mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }));
+ }
+ }
+
@VisibleForTesting
synchronized IBiometricsFingerprint getDaemon() {
if (mTestHalEnabled) {
@@ -518,6 +538,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
if (success) {
+ if (mCurrentUserId != targetUserId) {
+ // Create new session with updated user ID
+ mSession = null;
+ }
mCurrentUserId = targetUserId;
} else {
Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
@@ -554,47 +578,116 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
// Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
// thread.
mHandler.post(() -> {
- final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
- userId, mContext.getOpPackageName(), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, mLockoutTracker);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken);
+ } else {
+ scheduleResetLockoutHidl(sensorId, userId);
+ }
});
}
+ private void scheduleResetLockoutAidl(int sensorId, int userId,
+ @Nullable byte[] hardwareAuthToken) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, hardwareAuthToken, mLockoutTracker,
+ mLockoutResetDispatcher,
+ Utils.getCurrentStrength(sensorId));
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleResetLockoutHidl(int sensorId, int userId) {
+ final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
+ userId, mContext.getOpPackageName(), sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, mLockoutTracker);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- final FingerprintGenerateChallengeClient client =
- new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
+ } else {
+ scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
+ }
});
}
+ private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
+ final FingerprintGenerateChallengeClient client =
+ new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
- mContext, mLazyDaemon, token, userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
+ if (Flags.deHidl()) {
+ scheduleRevokeChallengeAidl(userId, token, opPackageName);
+ } else {
+ scheduleRevokeChallengeHidl(userId, token, opPackageName);
+ }
});
}
+ private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient(
+ mContext, this::getSession,
+ token, userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, 0L);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
+ private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
+ @NonNull String opPackageName) {
+ final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
+ mContext, mLazyDaemon, token, userId, opPackageName,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext);
+ mScheduler.scheduleClientMonitor(client);
+ }
+
@Override
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@@ -604,38 +697,96 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
- userId, hardwareAuthToken, opPackageName,
- FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
+ if (Flags.deHidl()) {
+ scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, enrollReason, id);
+ } else {
+ scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
+ opPackageName, enrollReason, id);
+ }
+ });
+ return id;
+ }
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
+ private void scheduleEnrollHidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+ @FingerprintManager.EnrollReason int enrollReason, long id) {
+ final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+ mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
+ userId, hardwareAuthToken, opPackageName,
+ FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+ true /* force */);
}
+ }
+ });
+ }
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (success) {
- // Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
- true /* force */);
- }
+ private void scheduleEnrollAidl(@NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+ @FingerprintManager.EnrollReason int enrollReason, long id) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
+ mContext,
+ this::getSession, token, id,
+ new ClientMonitorCallbackConverter(receiver),
+ userId, hardwareAuthToken, opPackageName,
+ FingerprintUtils.getLegacyInstance(mSensorId),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENROLL,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext, null /* sensorProps */,
+ mUdfpsOverlayController, mSidefpsController,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
+ enrollReason);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
+ @Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ if (success) {
+ // Update authenticatorIds
+ scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
+ true /* force */);
}
- });
+ }
});
- return id;
}
@Override
@@ -653,17 +804,46 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
scheduleUpdateActiveUserWithoutHandler(options.getUserId());
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
- final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+ if (Flags.deHidl()) {
+ scheduleFingerDetectAidl(token, listener, options, statsClient, id,
+ isStrongBiometric);
+ } else {
+ scheduleFingerDetectHidl(token, listener, options, statsClient, id,
+ isStrongBiometric);
+ }
});
return id;
}
+ private void scheduleFingerDetectHidl(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ int statsClient, long id, boolean isStrongBiometric) {
+ final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+ mLazyDaemon, token, id, listener, options,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleFingerDetectAidl(@NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ int statsClient, long id, boolean isStrongBiometric) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient(
+ mContext,
+ this::getSession, token, id, listener, options,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -674,20 +854,55 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
scheduleUpdateActiveUserWithoutHandler(options.getUserId());
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
- final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, requestId, listener, operationId,
- restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, isStrongBiometric,
- mTaskStackListener, mLockoutTracker,
- mUdfpsOverlayController, mSidefpsController,
- allowBackgroundAuthentication, mSensorProperties,
- Utils.getCurrentStrength(mSensorId));
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+
+ if (Flags.deHidl()) {
+ scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ } else {
+ scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId,
+ restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
+ }
});
}
+ private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient(
+ mContext, this::getSession, token, requestId, listener, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, isStrongBiometric,
+ mTaskStackListener,
+ mUdfpsOverlayController, mSidefpsController,
+ allowBackgroundAuthentication, mSensorProperties, mHandler,
+ Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
+ int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull FingerprintAuthenticateOptions options,
+ long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
+ final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+ mContext, mLazyDaemon, token, requestId, listener, operationId,
+ restricted, options, cookie, false /* requireConfirmation */,
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
+ mBiometricContext, isStrongBiometric,
+ mTaskStackListener, mLockoutTracker,
+ mUdfpsOverlayController, mSidefpsController,
+ allowBackgroundAuthentication, mSensorProperties,
+ Utils.getCurrentStrength(mSensorId));
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
int cookie, @NonNull ClientMonitorCallbackConverter listener,
@@ -719,17 +934,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
- userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName);
+ } else {
+ scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName);
+ }
});
}
+ private void scheduleRemoveHidl(@NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName) {
+ final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+ mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+ userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
+ private void scheduleRemoveAidl(@NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient(
+ mContext, this::getSession, token,
+ new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId,
+ opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId,
+ createLogger(BiometricsProtoEnums.ACTION_REMOVE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext, mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ }
+
@Override
public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int userId,
@@ -738,15 +977,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
scheduleUpdateActiveUserWithoutHandler(userId);
// For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver),
- 0 /* fingerprintId */, userId, opPackageName,
- FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
+ if (Flags.deHidl()) {
+ scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+ } else {
+ scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
+ }
});
}
@@ -755,17 +990,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
- mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext,
- FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, callback);
+ if (Flags.deHidl()) {
+ scheduleInternalCleanupAidl(userId, callback);
+ } else {
+ scheduleInternalCleanupHidl(userId, callback);
+ }
});
}
+ private void scheduleInternalCleanupHidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
+ mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+ mBiometricContext,
+ FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, callback);
+ }
+
+ private void scheduleInternalCleanupAidl(int userId,
+ @Nullable ClientMonitorCallback callback) {
+ final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient
+ client =
+ new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient(
+ mContext, this::getSession, userId, mContext.getOpPackageName(),
+ mSensorProperties.sensorId,
+ createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
+ mBiometricContext,
+ FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
+ mScheduler.scheduleClientMonitor(client, callback);
+ }
+
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
new file mode 100644
index 000000000000..c3e5cbe7daf4
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL
+ * response handler.
+ */
+public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub {
+
+ final AidlResponseHandler mAidlResponseHandler;
+
+ public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) {
+ mAidlResponseHandler = aidlResponseHandler;
+ }
+
+ @Override
+ public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+ mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining);
+ }
+
+ @Override
+ public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+ onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
+ mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onAuthenticated(long deviceId, int fingerId, int groupId,
+ ArrayList<Byte> token) {
+ if (fingerId != 0) {
+ byte[] hardwareAuthToken = new byte[token.size()];
+ for (int i = 0; i < token.size(); i++) {
+ hardwareAuthToken[i] = token.get(i);
+ }
+ mAidlResponseHandler.onAuthenticationSucceeded(fingerId,
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken));
+ } else {
+ mAidlResponseHandler.onAuthenticationFailed();
+ }
+ }
+
+ @Override
+ public void onError(long deviceId, int error, int vendorCode) {
+ mAidlResponseHandler.onError(error, vendorCode);
+ }
+
+ @Override
+ public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+ mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+ }
+
+ @Override
+ public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
+ mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+ }
+
+ void onChallengeGenerated(long challenge) {
+ mAidlResponseHandler.onChallengeGenerated(challenge);
+ }
+
+ void onChallengeRevoked(long challenge) {
+ mAidlResponseHandler.onChallengeRevoked(challenge);
+ }
+
+ void onResetLockout() {
+ mAidlResponseHandler.onLockoutCleared();
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 36d56c8a1544..0730c672acd9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -89,7 +89,8 @@ public class LockoutFrameworkImpl implements LockoutTracker {
// Attempt counter should only be cleared when Keyguard goes away or when
// a biometric is successfully authenticated. Lockout should eventually be done below the HAL.
// See AuthenticationClient#shouldFrameworkHandleLockout().
- void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
+ @Override
+ public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
if (getLockoutModeForUser(userId) != LOCKOUT_NONE) {
Slog.v(TAG, "Reset biometric lockout for user: " + userId
+ ", clearAttemptCounter: " + clearAttemptCounter);
@@ -104,7 +105,8 @@ public class LockoutFrameworkImpl implements LockoutTracker {
mLockoutResetCallback.onLockoutReset(userId);
}
- void addFailedAttemptForUser(int userId) {
+ @Override
+ public void addFailedAttemptForUser(int userId) {
mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1);
mTimedLockoutCleared.put(userId, false);
@@ -114,7 +116,8 @@ public class LockoutFrameworkImpl implements LockoutTracker {
}
@Override
- public @LockoutMode int getLockoutModeForUser(int userId) {
+ @LockoutMode
+ public int getLockoutModeForUser(int userId) {
final int failedAttempts = mFailedAttempts.get(userId, 0);
if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
return LOCKOUT_PERMANENT;
@@ -126,6 +129,19 @@ public class LockoutFrameworkImpl implements LockoutTracker {
return LOCKOUT_NONE;
}
+ /**
+ * Clears lockout for Fingerprint HIDL HAL
+ */
+ @Override
+ public void setLockoutModeForUser(int userId, int mode) {
+ mFailedAttempts.put(userId, 0);
+ mTimedLockoutCleared.put(userId, true);
+ // If we're asked to reset failed attempts externally (i.e. from Keyguard),
+ // the alarm might still be pending; remove it.
+ cancelLockoutResetForUser(userId);
+ mLockoutResetCallback.onLockoutReset(userId);
+ }
+
private void cancelLockoutResetForUser(int userId) {
mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 8736a53bb9f5..ac7d9c171247 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -221,9 +221,8 @@ public class SyncManager {
/** Flags used when connecting to a sync adapter service */
private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS =
- Context.BindServiceFlags.of(
- Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT);
+ Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT);
/** Singleton instance. */
@GuardedBy("SyncManager.class")
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 472c1f58dc8a..1ac3a12fad21 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -135,7 +135,8 @@ class DeviceStateToLayoutMap {
leadDisplayAddress,
d.getBrightnessThrottlingMapId(),
d.getRefreshRateZoneId(),
- d.getRefreshRateThermalThrottlingMapId());
+ d.getRefreshRateThermalThrottlingMapId(),
+ d.getPowerThrottlingMapId());
}
layout.postProcessLocked();
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 098cb87940b2..9f4f78794659 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -23,7 +23,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
import android.util.Slog;
@@ -201,20 +201,6 @@ abstract class DisplayDevice {
* @param state The new display state.
* @param brightnessState The new display brightnessState.
* @param sdrBrightnessState The new display brightnessState for SDR layers.
- * @return A runnable containing work to be deferred until after we have
- * exited the critical section, or null if none.
- */
- public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState) {
- return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null);
- }
-
- /**
- * Sets the display state, if supported.
- *
- * @param state The new display state.
- * @param brightnessState The new display brightnessState.
- * @param sdrBrightnessState The new display brightnessState for SDR layers.
* @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.
* @return A runnable containing work to be deferred until after we have exited the critical
* section, or null if none.
@@ -223,7 +209,7 @@ abstract class DisplayDevice {
int state,
float brightnessState,
float sdrBrightnessState,
- @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+ @Nullable DisplayOffloadSession displayOffloadSession) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0689478ded1e..35a4f475c878 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -494,6 +494,7 @@ public final class DisplayManagerService extends SystemService {
// If we would like to keep a particular eye on a package, we can set the package name.
private final boolean mExtraDisplayEventLogging;
+ private final String mExtraDisplayLoggingPackageName;
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
@@ -584,8 +585,8 @@ public final class DisplayManagerService extends SystemService {
mOverlayProperties = SurfaceControl.getOverlaySupport();
mSystemReady = false;
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
- final String name = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayEventLogging = !TextUtils.isEmpty(name);
+ mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
+ mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
}
public void setupSchedulerPolicies() {
@@ -2934,12 +2935,17 @@ public final class DisplayManagerService extends SystemService {
// Only send updates outside of DisplayManagerService for enabled displays
if (display.isEnabledLocked()) {
sendDisplayEventLocked(display, event);
+ } else if (mExtraDisplayEventLogging) {
+ Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
}
}
private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
int displayId = display.getDisplayIdLocked();
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+ if (mExtraDisplayEventLogging) {
+ Slog.i(TAG, "Deliver Display Event on Handler: " + event);
+ }
mHandler.sendMessage(msg);
}
@@ -3005,6 +3011,10 @@ public final class DisplayManagerService extends SystemService {
// For cached apps, save the pending event until it becomes non-cached
synchronized (mPendingCallbackSelfLocked) {
PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+ if (extraLogging(callbackRecord.mPackageName)) {
+ Slog.i(TAG,
+ "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+ }
if (pendingCallback == null) {
mPendingCallbackSelfLocked.put(uid,
new PendingCallback(callbackRecord, displayId, event));
@@ -3019,6 +3029,10 @@ public final class DisplayManagerService extends SystemService {
mTempCallbacks.clear();
}
+ private boolean extraLogging(String packageName) {
+ return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+ }
+
// Runs on Handler thread.
// Delivers display group event notifications to callbacks.
private void deliverDisplayGroupEvent(int groupId, int event) {
@@ -3462,6 +3476,7 @@ public final class DisplayManagerService extends SystemService {
public final int mUid;
private final IDisplayManagerCallback mCallback;
private @EventsMask AtomicLong mEventsMask;
+ private final String mPackageName;
public boolean mWifiDisplayScanRequested;
@@ -3471,6 +3486,9 @@ public final class DisplayManagerService extends SystemService {
mUid = uid;
mCallback = callback;
mEventsMask = new AtomicLong(eventsMask);
+
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ mPackageName = packageNames == null ? null : packageNames[0];
}
public void updateEventsMask(@EventsMask long eventsMask) {
@@ -3479,7 +3497,8 @@ public final class DisplayManagerService extends SystemService {
@Override
public void binderDied() {
- if (DEBUG) {
+ if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
+ mPackageName)) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
onCallbackDied(this);
@@ -3490,6 +3509,11 @@ public final class DisplayManagerService extends SystemService {
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
+ if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
+ mPackageName)) {
+ Slog.i(TAG,
+ "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 1652871963b9..7d9c0182a691 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -506,7 +506,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
mDisplayStatsId = mUniqueDisplayId.hashCode();
@@ -566,8 +565,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
- mDisplayDeviceConfig
- ), mContext);
+ logicalDisplay.getPowerThrottlingDataIdLocked(),
+ mDisplayDeviceConfig), mContext, flags);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
mAutomaticBrightnessStrategy =
@@ -821,10 +820,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
.getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;
final String thermalBrightnessThrottlingDataId =
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
-
- mBrightnessClamperController.onDisplayChanged(
- new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
- mThermalBrightnessThrottlingDataId, config));
+ final String powerThrottlingDataId =
+ mLogicalDisplay.getPowerThrottlingDataIdLocked();
mHandler.postAtTime(() -> {
boolean changed = false;
@@ -858,6 +855,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
mIsDisplayInternal = isDisplayInternal;
+ // using local variables here, when mBrightnessThrottler is removed,
+ // mThermalBrightnessThrottlingDataId could be removed as well
+ // changed = true will be not needed - clampers are maintaining their state and
+ // will call updatePowerState if needed.
+ mBrightnessClamperController.onDisplayChanged(
+ new BrightnessClamperController.DisplayDeviceData(uniqueId,
+ thermalBrightnessThrottlingDataId, powerThrottlingDataId, config));
+
if (changed) {
updatePowerState();
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index bd82b81513df..3d4209e0d6f3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -190,6 +190,11 @@ final class LogicalDisplay {
private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;
/**
+ * The ID of the power throttling data that should be used.
+ */
+ private String mPowerThrottlingDataId;
+
+ /**
* RefreshRateRange limitation for @Temperature.ThrottlingStatus
*/
@NonNull
@@ -205,6 +210,7 @@ final class LogicalDisplay {
mIsEnabled = true;
mIsInTransition = false;
mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
+ mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
}
@@ -911,6 +917,25 @@ final class LogicalDisplay {
}
/**
+ * @param powerThrottlingDataId The ID of the brightness throttling data that this
+ * display should use.
+ */
+ public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) {
+ if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) {
+ mPowerThrottlingDataId = powerThrottlingDataId;
+ mDirty = true;
+ }
+ }
+
+ /**
+ * Returns powerThrottlingDataId which is the ID of the brightness
+ * throttling data that this display should use.
+ */
+ public String getPowerThrottlingDataIdLocked() {
+ return mPowerThrottlingDataId;
+ }
+
+ /**
* Sets the display of which this display is a follower, regarding brightness or other
* properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any
* others, and has the potential to be a lead display to others.
@@ -976,6 +1001,7 @@ final class LogicalDisplay {
pw.println("mLeadDisplayId=" + mLeadDisplayId);
pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);
pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling);
+ pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId);
}
@Override
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index b3b16ade0546..c55bc62f5ae6 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1123,13 +1123,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
displayLayout.getRefreshRateThermalThrottlingMapId()
)
);
-
setEnabledLocked(newDisplay, displayLayout.isEnabled());
newDisplay.setThermalBrightnessThrottlingDataIdLocked(
displayLayout.getThermalBrightnessThrottlingMapId() == null
? DisplayDeviceConfig.DEFAULT_ID
: displayLayout.getThermalBrightnessThrottlingMapId());
-
+ newDisplay.setPowerThrottlingDataIdLocked(
+ displayLayout.getPowerThrottlingMapId() == null
+ ? DisplayDeviceConfig.DEFAULT_ID
+ : displayLayout.getPowerThrottlingMapId());
newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d910e16de8e8..b0025872aa3d 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -395,7 +396,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
@Override
public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState) {
+ float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 54a280fddb6c..68f72d3b085a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -52,7 +52,8 @@ abstract class BrightnessClamper<T> {
abstract void stop();
- enum Type {
- THERMAL
+ protected enum Type {
+ THERMAL,
+ POWER
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 14637afe4f76..787f786a97da 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -34,9 +34,12 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -48,11 +51,9 @@ import java.util.concurrent.Executor;
*/
public class BrightnessClamperController {
private static final String TAG = "BrightnessClamperController";
-
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final ClamperChangeListener mClamperChangeListenerExternal;
-
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
@@ -64,13 +65,15 @@ public class BrightnessClamperController {
private boolean mClamperApplied = false;
public BrightnessClamperController(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
- this(new Injector(), handler, clamperChangeListener, data, context);
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+ this(new Injector(), handler, clamperChangeListener, data, context, flags);
}
@VisibleForTesting
BrightnessClamperController(Injector injector, Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
mHandler = handler;
mClamperChangeListenerExternal = clamperChangeListener;
@@ -84,7 +87,7 @@ public class BrightnessClamperController {
}
};
- mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data);
+ mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);
mModifiers = injector.getModifiers(context);
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
@@ -144,6 +147,8 @@ public class BrightnessClamperController {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
} else if (mClamperType == Type.THERMAL) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
+ } else if (mClamperType == Type.POWER) {
+ return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
} else {
Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -193,6 +198,7 @@ public class BrightnessClamperController {
mClamperType = clamperType;
mClamperChangeListenerExternal.onChanged();
}
+
}
private void start() {
@@ -219,10 +225,15 @@ public class BrightnessClamperController {
}
List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
+ DisplayManagerFlags flags) {
List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
clampers.add(
new BrightnessThermalClamper(handler, clamperChangeListener, data));
+ if (flags.isPowerThrottlingClamperEnabled()) {
+ clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
+ data));
+ }
return clampers;
}
@@ -235,21 +246,26 @@ public class BrightnessClamperController {
}
/**
- * Data for clampers
+ * Config Data for clampers
*/
- public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+ public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData,
+ BrightnessPowerClamper.PowerData {
@NonNull
private final String mUniqueDisplayId;
@NonNull
private final String mThermalThrottlingDataId;
+ @NonNull
+ private final String mPowerThrottlingDataId;
private final DisplayDeviceConfig mDisplayDeviceConfig;
public DisplayDeviceData(@NonNull String uniqueDisplayId,
@NonNull String thermalThrottlingDataId,
+ @NonNull String powerThrottlingDataId,
@NonNull DisplayDeviceConfig displayDeviceConfig) {
mUniqueDisplayId = uniqueDisplayId;
mThermalThrottlingDataId = thermalThrottlingDataId;
+ mPowerThrottlingDataId = powerThrottlingDataId;
mDisplayDeviceConfig = displayDeviceConfig;
}
@@ -272,5 +288,24 @@ public class BrightnessClamperController {
return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
mThermalThrottlingDataId);
}
+
+ @NonNull
+ @Override
+ public String getPowerThrottlingDataId() {
+ return mPowerThrottlingDataId;
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingData getPowerThrottlingData() {
+ return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get(
+ mPowerThrottlingDataId);
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+ return mDisplayDeviceConfig.getPowerThrottlingConfigData();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
new file mode 100644
index 000000000000..339b5896f3f7
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessPowerClamper extends
+ BrightnessClamper<BrightnessPowerClamper.PowerData> {
+
+ private static final String TAG = "BrightnessPowerClamper";
+ @NonNull
+ private final Injector mInjector;
+ @NonNull
+ private final DeviceConfigParameterProvider mConfigParameterProvider;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final ClamperChangeListener mChangeListener;
+ @Nullable
+ private PmicMonitor mPmicMonitor;
+ // data from DeviceConfig, for all displays, for all dataSets
+ // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData))
+ @NonNull
+ private Map<String, Map<String, PowerThrottlingData>>
+ mPowerThrottlingDataOverride = Map.of();
+ // data from DisplayDeviceConfig, for particular display+dataSet
+ @Nullable
+ private PowerThrottlingData mPowerThrottlingDataFromDDC = null;
+ // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId,
+ // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC.
+ @Nullable
+ private PowerThrottlingData mPowerThrottlingDataActive = null;
+ @Nullable
+ private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
+
+ private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
+ private float mCurrentAvgPowerConsumed = 0;
+ @Nullable
+ private String mUniqueDisplayId = null;
+ @Nullable
+ private String mDataId = null;
+
+ private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+ try {
+ int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+ float powerQuota = Float.parseFloat(value);
+ return new ThrottlingLevel(status, powerQuota);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ };
+
+ private final Function<List<ThrottlingLevel>, PowerThrottlingData>
+ mDataSetMapper = PowerThrottlingData::create;
+
+
+ BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
+ PowerData powerData) {
+ this(new Injector(), handler, listener, powerData);
+ }
+
+ @VisibleForTesting
+ BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
+ PowerData powerData) {
+ mInjector = injector;
+ mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+ mHandler = handler;
+ mChangeListener = listener;
+
+ mHandler.post(() -> {
+ setDisplayData(powerData);
+ loadOverrideData();
+ start();
+ });
+
+ }
+
+ @Override
+ @NonNull
+ BrightnessClamper.Type getType() {
+ return Type.POWER;
+ }
+
+ @Override
+ void onDeviceConfigChanged() {
+ mHandler.post(() -> {
+ loadOverrideData();
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void onDisplayChanged(PowerData data) {
+ mHandler.post(() -> {
+ setDisplayData(data);
+ recalculateActiveData();
+ });
+ }
+
+ @Override
+ void stop() {
+ if (mPmicMonitor != null) {
+ mPmicMonitor.shutdown();
+ }
+ }
+
+ /**
+ * Dumps the state of BrightnessPowerClamper.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessPowerClamper:");
+ pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
+ pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
+ pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel);
+ pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
+ : mPowerThrottlingDataFromDDC.toString()));
+ super.dump(pw);
+ }
+
+ private void recalculateActiveData() {
+ if (mUniqueDisplayId == null || mDataId == null) {
+ return;
+ }
+ mPowerThrottlingDataActive = mPowerThrottlingDataOverride
+ .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+ mPowerThrottlingDataFromDDC);
+ if (mPowerThrottlingDataActive != null) {
+ if (mPmicMonitor != null) {
+ mPmicMonitor.stop();
+ mPmicMonitor.start();
+ }
+ } else {
+ if (mPmicMonitor != null) {
+ mPmicMonitor.stop();
+ }
+ }
+ recalculateBrightnessCap();
+ }
+
+ private void loadOverrideData() {
+ String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData();
+ mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+ throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+ }
+
+ private void setDisplayData(@NonNull PowerData data) {
+ mUniqueDisplayId = data.getUniqueDisplayId();
+ mDataId = data.getPowerThrottlingDataId();
+ mPowerThrottlingDataFromDDC = data.getPowerThrottlingData();
+ if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) {
+ Slog.wtf(TAG,
+ "Power throttling data is missing for powerThrottlingDataId=" + mDataId);
+ }
+
+ mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
+ if (mPowerThrottlingConfigData == null) {
+ Slog.d(TAG,
+ "Power throttling data is missing for configuration data.");
+ }
+ }
+
+ private void recalculateBrightnessCap() {
+ boolean isActive = false;
+ float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+ float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel);
+ if (mPowerThrottlingDataActive == null) {
+ return;
+ }
+ if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) {
+ isActive = true;
+ // calculate new brightness Cap.
+ // Brightness has a linear relation to power-consumed.
+ targetBrightnessCap =
+ (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX;
+ // Cap to lowest allowed brightness on device.
+ targetBrightnessCap = Math.max(targetBrightnessCap,
+ mPowerThrottlingConfigData.brightnessLowestCapAllowed);
+ }
+
+ if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
+ mIsActive = isActive;
+ mBrightnessCap = targetBrightnessCap;
+ mChangeListener.onChanged();
+ }
+ }
+
+ private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) {
+ float powerQuota = 0f;
+ if (mPowerThrottlingDataActive != null) {
+ // Throttling levels are sorted by increasing severity
+ for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) {
+ if (level.thermalStatus <= thermalStatus) {
+ powerQuota = level.powerQuotaMilliWatts;
+ } else {
+ // Throttling levels that are greater than the current status are irrelevant
+ break;
+ }
+ }
+ }
+ return powerQuota;
+ }
+
+ private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
+ mHandler.post(() -> {
+ mCurrentThermalLevel = thermalStatus;
+ mCurrentAvgPowerConsumed = avgPowerConsumed;
+ recalculateBrightnessCap();
+ });
+ }
+
+ private void start() {
+ if (mPowerThrottlingConfigData == null) {
+ return;
+ }
+ PowerChangeListener listener = (powerConsumed, thermalStatus) -> {
+ recalculatePowerQuotaChange(powerConsumed, thermalStatus);
+ };
+ mPmicMonitor =
+ mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis);
+ mPmicMonitor.start();
+ }
+
+ public interface PowerData {
+ @NonNull
+ String getUniqueDisplayId();
+
+ @NonNull
+ String getPowerThrottlingDataId();
+
+ @Nullable
+ PowerThrottlingData getPowerThrottlingData();
+
+ @Nullable
+ PowerThrottlingConfigData getPowerThrottlingConfigData();
+ }
+
+ /**
+ * Power change listener
+ */
+ @FunctionalInterface
+ public interface PowerChangeListener {
+ /**
+ * Notifies that power state changed from power controller.
+ */
+ void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus);
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) {
+ return new PmicMonitor(listener, pollingTime);
+ }
+
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
new file mode 100644
index 000000000000..26784f2353ff
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.IThermalService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.power.PowerStatsInternal;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Monitors the display consumed power and helps make informed decision,
+ * regarding overconsumption.
+ */
+public class PmicMonitor {
+ private static final String TAG = "PmicMonitor";
+
+ // The executor to periodically monitor the display power.
+ private final ScheduledExecutorService mExecutor;
+ @NonNull
+ private final PowerChangeListener mPowerChangeListener;
+ private final long mPowerMonitorPeriodConfigSecs;
+ private final PowerStatsInternal mPowerStatsInternal;
+ @VisibleForTesting final IThermalService mThermalService;
+ private ScheduledFuture<?> mPmicMonitorFuture;
+ private float mLastEnergyConsumed = 0;
+ private float mCurrentAvgPower = 0;
+ private Temperature mCurrentTemperature;
+ private long mCurrentTimestampMillis = 0;
+
+ PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) {
+ mPowerChangeListener = listener;
+ mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+ mThermalService = IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ // start a periodic worker thread.
+ mExecutor = Executors.newSingleThreadScheduledExecutor();
+ mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs;
+ }
+
+ @Nullable
+ private Temperature getDisplayTemperature() {
+ Temperature retTemperature = null;
+ try {
+ Temperature[] temperatures;
+ // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
+ temperatures = mThermalService.getCurrentTemperaturesWithType(
+ Temperature.TYPE_SKIN);
+ if (temperatures.length > 1) {
+ Slog.w(TAG, "Multiple skin temperatures not allowed!");
+ }
+ if (temperatures.length > 0) {
+ retTemperature = temperatures[0];
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "getDisplayTemperature failed" + e);
+ }
+ return retTemperature;
+ }
+
+ private void capturePeriodicDisplayPower() {
+ final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo();
+ if (energyConsumers == null || energyConsumers.length == 0) {
+ return;
+ }
+ final IntArray energyConsumerIds = new IntArray();
+ for (int i = 0; i < energyConsumers.length; i++) {
+ if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) {
+ energyConsumerIds.add(energyConsumers[i].id);
+ }
+ }
+
+ if (energyConsumerIds.size() == 0) {
+ Slog.w(TAG, "DISPLAY energyConsumerIds size is null");
+ return;
+ }
+ CompletableFuture<EnergyConsumerResult[]> futureECRs =
+ mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray());
+ if (futureECRs == null) {
+ Slog.w(TAG, "Energy consumers results are null");
+ return;
+ }
+
+ EnergyConsumerResult[] displayResults;
+ try {
+ displayResults = futureECRs.get();
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e);
+ displayResults = null;
+ } catch (ExecutionException e) {
+ Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e);
+ displayResults = null;
+ }
+
+ if (displayResults == null || displayResults.length == 0) {
+ Slog.w(TAG, "displayResults are null");
+ return;
+ }
+ // Support for only 1 display rail.
+ float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed);
+ float timeIntervalSeconds =
+ (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f;
+ // energy consumed is received in microwatts-seconds.
+ float currentPower = energyConsumed / timeIntervalSeconds;
+ // convert power received in microwatts to milliwatts.
+ currentPower = currentPower / 1000.f;
+
+ // capture thermal state.
+ Temperature displayTemperature = getDisplayTemperature();
+ mCurrentAvgPower = currentPower;
+ mCurrentTemperature = displayTemperature;
+ mLastEnergyConsumed = displayResults[0].energyUWs;
+ mCurrentTimestampMillis = displayResults[0].timestampMs;
+ if (mCurrentTemperature != null) {
+ mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+ }
+ }
+
+ /**
+ * Start polling the power IC.
+ */
+ public void start() {
+ if (mPowerStatsInternal == null) {
+ Slog.w(TAG, "Power stats service not found for monitoring.");
+ return;
+ }
+ if (mThermalService == null) {
+ Slog.w(TAG, "Thermal service not found.");
+ return;
+ }
+ if (mPmicMonitorFuture == null) {
+ mPmicMonitorFuture = mExecutor.scheduleAtFixedRate(
+ this::capturePeriodicDisplayPower,
+ mPowerMonitorPeriodConfigSecs,
+ mPowerMonitorPeriodConfigSecs,
+ TimeUnit.SECONDS);
+ } else {
+ Slog.e(TAG, "already scheduled, stop() called before start.");
+ }
+ }
+
+ /**
+ * Stop polling to power IC.
+ */
+ public void stop() {
+ if (mPmicMonitorFuture != null) {
+ mPmicMonitorFuture.cancel(true);
+ mPmicMonitorFuture = null;
+ }
+ }
+
+ /**
+ * Shutdown power IC service and worker thread.
+ */
+ public void shutdown() {
+ mExecutor.shutdownNow();
+ }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 03b0cfc3d844..e3aa161f001a 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -234,7 +234,9 @@ public final class ColorDisplayService extends SystemService {
}
}
- @VisibleForTesting void onUserChanged(int userHandle) {
+ // should be called in handler thread (same thread that started animation)
+ @VisibleForTesting
+ void onUserChanged(int userHandle) {
final ContentResolver cr = getContext().getContentResolver();
if (mCurrentUser != UserHandle.USER_NULL) {
@@ -473,6 +475,15 @@ public final class ColorDisplayService extends SystemService {
}
}
+ // should be called in handler thread (same thread that started animation)
+ @VisibleForTesting
+ void cancelAllAnimators() {
+ mNightDisplayTintController.cancelAnimator();
+ mGlobalSaturationTintController.cancelAnimator();
+ mReduceBrightColorsTintController.cancelAnimator();
+ mDisplayWhiteBalanceTintController.cancelAnimator();
+ }
+
private boolean resetReduceBrightColors() {
if (mCurrentUser == UserHandle.USER_NULL) {
return false;
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index 23ffe5948da8..465584c3d90c 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -77,6 +77,12 @@ public class DeviceConfigParameterProvider {
// Test parameters
// usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
+ // allows to customize power throttling data
+ public String getPowerThrottlingData() {
+ return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null);
+ }
+
// allows to customize brightness throttling data
public String getBrightnessThrottlingData() {
return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 7050c5a4168f..fae8383bb62e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -67,20 +67,31 @@ public class DisplayManagerFlags {
Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE,
Flags::backUpSmoothDisplayAndForcePeakRefreshRate);
+ private final FlagState mPowerThrottlingClamperFlagState = new FlagState(
+ Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
+ Flags::enablePowerThrottlingClamper);
+
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
return mConnectedDisplayManagementFlagState.isEnabled();
}
- /** Returns whether hdr clamper is enabled on not*/
+ /** Returns whether NBM Controller is enabled or not. */
public boolean isNbmControllerEnabled() {
return mNbmControllerFlagState.isEnabled();
}
+ /** Returns whether hdr clamper is enabled on not. */
public boolean isHdrClamperEnabled() {
return mHdrClamperFlagState.isEnabled();
}
+ /** Returns whether power throttling clamper is enabled on not. */
+ public boolean isPowerThrottlingClamperEnabled() {
+ return mPowerThrottlingClamperFlagState.isEnabled();
+ }
+
+
/**
* Returns whether adaptive tone improvements are enabled
*/
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index a85e10dcfe2e..9ab9c9def61b 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -27,6 +27,14 @@ flag {
}
flag {
+ name: "enable_power_throttling_clamper"
+ namespace: "display_manager"
+ description: "Feature flag for Power Throttling Clamper"
+ bug: "294777007"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_adaptive_tone_improvements_1"
namespace: "display_manager"
description: "Feature flag for Adaptive Tone Improvements"
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index d9ec3de46b0e..40cb3303adda 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -80,7 +80,8 @@ public class Layout {
createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
/* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
}
/**
@@ -97,6 +98,7 @@ public class Layout {
* @param refreshRateZoneId Layout limited refresh rate zone name.
* @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
* policy should be used.
+ * @param powerThrottlingMapId Name of which power throttling policy should be used.
*
* @exception IllegalArgumentException When a default display owns a display group other than
* DEFAULT_DISPLAY_GROUP.
@@ -106,7 +108,8 @@ public class Layout {
String displayGroupName, DisplayIdProducer idProducer, int position,
@Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
@Nullable String refreshRateZoneId,
- @Nullable String refreshRateThermalThrottlingMapId) {
+ @Nullable String refreshRateThermalThrottlingMapId,
+ @Nullable String powerThrottlingMapId) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return;
@@ -139,7 +142,7 @@ public class Layout {
final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
- refreshRateThermalThrottlingMapId);
+ refreshRateThermalThrottlingMapId, powerThrottlingMapId);
mDisplays.add(display);
}
@@ -311,6 +314,9 @@ public class Layout {
@Nullable
private final String mThermalRefreshRateThrottlingMapId;
+ @Nullable
+ private final String mPowerThrottlingMapId;
+
// The ID of the lead display that this display will follow in a layout. -1 means no lead.
// This is determined using {@code mLeadDisplayAddress}.
private int mLeadDisplayId;
@@ -318,7 +324,8 @@ public class Layout {
private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
@NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
@Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
- @Nullable String refreshRateThermalThrottlingMapId) {
+ @Nullable String refreshRateThermalThrottlingMapId,
+ @Nullable String powerThrottlingMapId) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
@@ -328,6 +335,7 @@ public class Layout {
mLeadDisplayAddress = leadDisplayAddress;
mRefreshRateZoneId = refreshRateZoneId;
mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
+ mPowerThrottlingMapId = powerThrottlingMapId;
mLeadDisplayId = NO_LEAD_DISPLAY;
}
@@ -344,6 +352,7 @@ public class Layout {
+ ", mLeadDisplayId: " + mLeadDisplayId
+ ", mLeadDisplayAddress: " + mLeadDisplayAddress
+ ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
+ + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId
+ "}";
}
@@ -366,7 +375,9 @@ public class Layout {
&& this.mLeadDisplayId == otherDisplay.mLeadDisplayId
&& Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
&& Objects.equals(mThermalRefreshRateThrottlingMapId,
- otherDisplay.mThermalRefreshRateThrottlingMapId);
+ otherDisplay.mThermalRefreshRateThrottlingMapId)
+ && Objects.equals(mPowerThrottlingMapId,
+ otherDisplay.mPowerThrottlingMapId);
}
@Override
@@ -382,6 +393,7 @@ public class Layout {
result = 31 * result + mLeadDisplayId;
result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
+ result = 31 * result + Objects.hashCode(mPowerThrottlingMapId);
return result;
}
@@ -441,6 +453,15 @@ public class Layout {
return mThermalRefreshRateThrottlingMapId;
}
+ /**
+ * Gets the id of the power throttling map that should be used.
+ * @return The ID of the power throttling map that this display should use,
+ * null if unspecified, will fall back to default.
+ */
+ public String getPowerThrottlingMapId() {
+ return mPowerThrottlingMapId;
+ }
+
private void setLeadDisplayId(int id) {
mLeadDisplayId = id;
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index b090334db352..27e1b9a303f2 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -60,84 +60,99 @@ public final class ImeTrackerService extends IImeTracker.Stub {
private static final long TIMEOUT_MS = 10_000;
/** Handler for registering timeouts for live entries. */
+ @GuardedBy("mLock")
private final Handler mHandler;
/** Singleton instance of the History. */
- @GuardedBy("ImeTrackerService.this")
+ @GuardedBy("mLock")
private final History mHistory = new History();
+ private final Object mLock = new Object();
+
ImeTrackerService(@NonNull Looper looper) {
mHandler = new Handler(looper, null /* callback */, true /* async */);
}
@NonNull
@Override
- public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
+ public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,
@ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final var binder = new Binder();
final var token = new ImeTracker.Token(binder, tag);
final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,
origin, reason);
- mHistory.addEntry(binder, entry);
-
- // Register a delayed task to handle the case where the new entry times out.
- mHandler.postDelayed(() -> {
- synchronized (ImeTrackerService.this) {
- mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
- }
- }, TIMEOUT_MS);
-
+ synchronized (mLock) {
+ mHistory.addEntry(binder, entry);
+
+ // Register a delayed task to handle the case where the new entry times out.
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+ ImeTracker.PHASE_NOT_SET);
+ }
+ }, TIMEOUT_MS);
+ }
return token;
}
@NonNull
@Override
- public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
+ public ImeTracker.Token onRequestHide(@NonNull String tag, int uid,
@ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
final var binder = new Binder();
final var token = new ImeTracker.Token(binder, tag);
final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,
origin, reason);
- mHistory.addEntry(binder, entry);
-
- // Register a delayed task to handle the case where the new entry times out.
- mHandler.postDelayed(() -> {
- synchronized (ImeTrackerService.this) {
- mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
- }
- }, TIMEOUT_MS);
-
+ synchronized (mLock) {
+ mHistory.addEntry(binder, entry);
+
+ // Register a delayed task to handle the case where the new entry times out.
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT,
+ ImeTracker.PHASE_NOT_SET);
+ }
+ }, TIMEOUT_MS);
+ }
return token;
}
@Override
- public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
- final var entry = mHistory.getEntry(binder);
- if (entry == null) return;
+ public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) {
+ synchronized (mLock) {
+ final var entry = mHistory.getEntry(binder);
+ if (entry == null) return;
- entry.mPhase = phase;
+ entry.mPhase = phase;
+ }
}
@Override
- public synchronized void onFailed(@NonNull ImeTracker.Token statsToken,
- @ImeTracker.Phase int phase) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+ public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+ }
}
@Override
- public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken,
- @ImeTracker.Phase int phase) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+ public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+ }
}
@Override
- public synchronized void onShown(@NonNull ImeTracker.Token statsToken) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ public void onShown(@NonNull ImeTracker.Token statsToken) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
}
@Override
- public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) {
- mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ public void onHidden(@NonNull ImeTracker.Token statsToken) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
}
/**
@@ -146,25 +161,30 @@ public final class ImeTrackerService extends IImeTracker.Stub {
* @param statsToken the token corresponding to the current IME request.
* @param requestWindowName the name of the window that created the IME request.
*/
- public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
+ public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,
@NonNull String requestWindowName) {
- final var entry = mHistory.getEntry(statsToken.getBinder());
- if (entry == null) return;
+ synchronized (mLock) {
+ final var entry = mHistory.getEntry(statsToken.getBinder());
+ if (entry == null) return;
- entry.mRequestWindowName = requestWindowName;
+ entry.mRequestWindowName = requestWindowName;
+ }
}
/** Dumps the contents of the history. */
- public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- mHistory.dump(pw, prefix);
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ synchronized (mLock) {
+ mHistory.dump(pw, prefix);
+ }
}
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@Override
- public synchronized boolean hasPendingImeVisibilityRequests() {
+ public boolean hasPendingImeVisibilityRequests() {
super.hasPendingImeVisibilityRequests_enforcePermission();
-
- return !mHistory.mLiveEntries.isEmpty();
+ synchronized (mLock) {
+ return !mHistory.mLiveEntries.isEmpty();
+ }
}
/**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1c5ecb70a3ae..1e8b387fc189 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -45,7 +45,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index eb997badca52..8bc69c226d1a 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -345,7 +345,7 @@ import java.util.Set;
}
private void notifyBluetoothRoutesUpdated() {
- mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated();
}
private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 93f6ff3de3c2..33190ade4f42 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -231,7 +231,7 @@ import java.util.Objects;
}
if (isDeviceRouteChanged) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
}
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index ddeeacc76579..2b01001fd7d1 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -136,12 +136,8 @@ import java.util.Objects;
*/
interface BluetoothRoutesUpdatedListener {
- /**
- * Called when Bluetooth routes have changed.
- *
- * @param routes updated Bluetooth routes list.
- */
- void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+ /** Called when Bluetooth routes have changed. */
+ void onBluetoothRoutesUpdated();
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index e17f4a3fd42f..7876095a548a 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -93,12 +93,8 @@ import com.android.media.flags.Flags;
*/
interface OnDeviceRouteChangedListener {
- /**
- * Called when device route has changed.
- *
- * @param deviceRoute non-null device route.
- */
- void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+ /** Called when device route has changed. */
+ void onDeviceRouteChanged();
}
}
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 198040378dc6..ba3cecf7c091 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -280,7 +280,7 @@ class LegacyBluetoothRouteController implements BluetoothRouteController {
private void notifyBluetoothRoutesUpdated() {
if (mListener != null) {
- mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated();
}
}
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 971d11f24b9c..6ba40ae33f3c 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -165,8 +165,8 @@ import java.util.Objects;
}
}
- private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ private void notifyDeviceRouteUpdate() {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@@ -177,7 +177,7 @@ import java.util.Objects;
synchronized (LegacyDeviceRouteController.this) {
mDeviceRoute = deviceRoute;
}
- notifyDeviceRouteUpdate(deviceRoute);
+ notifyDeviceRouteUpdate();
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 95ca08cc7fe9..a158b18d91b4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -97,20 +97,23 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
/**
- * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link
- * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid
- * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and
- * above. For apps targeting Android T and below, the request will be ignored.
+ * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver(
+ * android.content.ComponentName)} throws an {@link
+ * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName}
+ * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver}
+ * for apps targeting Android U and above. For apps targeting Android T and below, the request
+ * will be ignored.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
/**
- * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link
- * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link
- * android.app.Activity activity} for apps targeting Android V and above. For apps targeting
- * Android U and below, the request will be ignored.
+ * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)}
+ * throws an {@link java.lang.IllegalArgumentException} if the provided
+ * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for
+ * apps targeting Android V and above. For apps targeting Android U and below, the request will
+ * be ignored.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 803ab28f2a65..0e8f90795ef9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,6 +19,7 @@ package com.android.server.media;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.UserHandle.ALL;
import static android.os.UserHandle.CURRENT;
+
import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;
import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;
import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden;
@@ -1904,6 +1905,15 @@ public class MediaSessionService extends SystemService implements Monitor {
keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
return;
}
+ if (Flags.fallbackToDefaultHandlingWhenMediaSessionHasFixedVolumeHandling()
+ && !record.canHandleVolumeKey()) {
+ Log.d(TAG, "Session with packageName=" + record.getPackageName()
+ + " doesn't support volume adjustment."
+ + " Fallbacks to the default handling.");
+ dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, true,
+ keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
+ return;
+ }
switch (keyEvent.getAction()) {
case KeyEvent.ACTION_DOWN: {
int direction = 0;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6c9aa4b0d849..67a1ccd17835 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -109,21 +109,28 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
-
- mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
- mHandler.post(() -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
- });
+ mBluetoothRouteController =
+ BluetoothRouteController.createInstance(
+ context,
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+
+ mDeviceRouteController =
+ DeviceRouteController.createInstance(
+ context,
+ () -> {
+ mHandler.post(
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ });
mAudioManager.addOnDevicesForAttributesChangedListener(
AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 13d166294603..8cbc368467bb 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,6 +76,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -134,6 +135,7 @@ public final class MediaProjectionManagerService extends SystemService
private final MediaRouter mMediaRouter;
private final MediaRouterCallback mMediaRouterCallback;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private MediaRouter.RouteInfo mMediaRouteInfo;
@GuardedBy("mLock")
@@ -160,6 +162,7 @@ public final class MediaProjectionManagerService extends SystemService
mWmInternal = LocalServices.getService(WindowManagerInternal.class);
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
+ mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();
Watchdog.getInstance().addMonitor(this);
}
@@ -193,6 +196,10 @@ public final class MediaProjectionManagerService extends SystemService
Looper createCallbackLooper() {
return Looper.getMainLooper();
}
+
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+ return MediaProjectionMetricsLogger.getInstance();
+ }
}
@Override
@@ -372,6 +379,10 @@ public final class MediaProjectionManagerService extends SystemService
if (mProjectionGrant != null) {
// Cache the session details.
mProjectionGrant.mSession = incomingSession;
+ mMediaProjectionMetricsLogger.notifyProjectionStateChange(
+ mProjectionGrant.uid,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
}
return true;
@@ -818,6 +829,19 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyPermissionRequestStateChange(int hostUid, int state,
+ int sessionCreationSource) {
+ notifyPermissionRequestStateChange_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
new file mode 100644
index 000000000000..f18ecad09c42
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media.projection;
+
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Class for emitting logs describing a MediaProjection session.
+ */
+public class MediaProjectionMetricsLogger {
+ private static MediaProjectionMetricsLogger sSingleton = null;
+
+ public static MediaProjectionMetricsLogger getInstance() {
+ if (sSingleton == null) {
+ sSingleton = new MediaProjectionMetricsLogger();
+ }
+ return sSingleton;
+ }
+
+ void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
+ write(hostUid, state, sessionCreationSource);
+ }
+
+ private void write(int hostUid, int state, int sessionCreationSource) {
+ FrameworkStatsLog.write(
+ /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+ /* session_id */ 123,
+ /* state */ state,
+ /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+ /* host_uid */ hostUid,
+ /* target_uid */ -1,
+ /* time_since_last_active */ 0,
+ /* creation_source */ sessionCreationSource);
+ }
+}
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 4c6110b99efd..d00268899a07 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -35,8 +35,8 @@ import android.service.oemlock.IOemLockService;
import android.util.Slog;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
import com.android.server.pm.UserRestrictionsUtils;
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index c7c8136ff0da..79c13461fc6b 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -569,6 +569,10 @@ public final class NativeTombstoneManager {
@Override
public void onEvent(int event, @Nullable String path) {
+ if (path == null) {
+ Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()");
+ return;
+ }
mHandler.post(() -> {
// Ignore .tmp files.
if (path.endsWith(".tmp")) {
diff --git a/services/core/java/com/android/server/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS
new file mode 100644
index 000000000000..6f322eea0f2e
--- /dev/null
+++ b/services/core/java/com/android/server/pdb/OWNERS
@@ -0,0 +1,2 @@
+victorhsieh@google.com
+swillden@google.com
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
index 21fa9f9a9401..66ad7169d6ec 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pdb;
/**
* Internal interface for storing and retrieving persistent data.
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 754a7ede8006..b006ac862533 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pdb;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -37,6 +37,9 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 7db7bf538c37..30017be96085 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -505,6 +505,10 @@ public class ComputerEngine implements Computer {
int filterCallingUid, int userId, boolean resolveForStart,
boolean allowDynamicSplits) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
+
+ // Allow to match activities of quarantined packages.
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
@@ -647,11 +651,6 @@ public class ComputerEngine implements Computer {
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
- // Only if the query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
@@ -4047,9 +4046,6 @@ public class ComputerEngine implements Computer {
flags = updateFlagsForComponent(flags, userId);
enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "get provider info");
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
ParsedProvider p = mComponentResolver.getProvider(component);
if (DEBUG_PACKAGE_INFO) Log.v(
TAG, "getProviderInfo " + component + ": " + p);
@@ -4679,9 +4675,6 @@ public class ComputerEngine implements Computer {
int callingUid) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId);
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,
userId);
boolean checkedGrants = false;
@@ -4794,13 +4787,6 @@ public class ComputerEngine implements Computer {
false /* checkShell */, "queryContentProviders");
if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForComponent(flags, userId);
-
- // Only if the service query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
-
ArrayList<ProviderInfo> finalList = null;
final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName,
metaDataKey, uid, flags, userId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index edb45aa0ffb4..29678181679b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1269,10 +1269,9 @@ final class InstallPackageHelper {
replace = true;
if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
}
- final AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
- if (replace && oldPackage != null) {
+ if (replace) {
// Prevent apps opting out from runtime permissions
- final int oldTargetSdk = oldPackage.getTargetSdkVersion();
+ final int oldTargetSdk = ps.getTargetSdkVersion();
final int newTargetSdk = parsedPackage.getTargetSdkVersion();
if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
&& newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -1284,10 +1283,10 @@ final class InstallPackageHelper {
+ " target SDK " + oldTargetSdk + " does.");
}
// Prevent persistent apps from being updated
- if (oldPackage.isPersistent()
+ if (ps.isPersistent()
&& ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
- "Package " + oldPackage.getPackageName() + " is a persistent app. "
+ "Package " + pkgName + " is a persistent app. "
+ "Persistent apps are not updateable.");
}
}
@@ -1668,7 +1667,7 @@ final class InstallPackageHelper {
}
// don't allow a system upgrade unless the upgrade hash matches
- if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null
+ if (oldPackageState.getRestrictUpdateHash() != null
&& oldPackageState.isSystem()) {
final byte[] digestBytes;
try {
@@ -1684,12 +1683,13 @@ final class InstallPackageHelper {
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"Could not compute hash: " + pkgName11);
}
- if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
+ if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) {
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"New package fails restrict-update check: " + pkgName11);
}
// retain upgrade restriction
- parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
+ parsedPackage.setRestrictUpdateHash(
+ oldPackageState.getRestrictUpdateHash());
}
if (oldPackage != null) {
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 148e0df7cf64..9ad8318c2b5f 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -89,6 +89,21 @@ public final class MovePackageHelper {
if (packageState == null || packageState.getPkg() == null) {
throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
}
+ final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
+ mPm.mUserManager.getUserIds(), true);
+ final UserHandle userForMove;
+ if (installedUserIds.length > 0) {
+ userForMove = UserHandle.of(installedUserIds[0]);
+ } else {
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST,
+ "Package is not installed for any user");
+ }
+ for (int userId : installedUserIds) {
+ if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid,
+ userId)) {
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
+ }
+ }
final AndroidPackage pkg = packageState.getPkg();
if (packageState.isSystem()) {
throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
@@ -134,8 +149,6 @@ public final class MovePackageHelper {
final String label = String.valueOf(pm.getApplicationLabel(
AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
final int targetSdkVersion = pkg.getTargetSdkVersion();
- final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
- mPm.mUserManager.getUserIds(), true);
final String fromCodePath;
if (codeFile.getParentFile().getName().startsWith(
PackageManagerService.RANDOM_DIR_PREFIX)) {
@@ -303,8 +316,8 @@ public final class MovePackageHelper {
final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
final InstallingSession installingSession = new InstallingSession(origin, move,
installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
- volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
- lite, mPm);
+ volumeUuid, userForMove, packageAbiOverride,
+ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
installingSession.movePackage();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68aa93d28330..abeabc96e969 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1469,8 +1469,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
archPkg.versionCode = (int) longVersionCode;
- // TODO(b/297916136): extract target sdk version.
- archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+ archPkg.targetSdkVersion = ps.getTargetSdkVersion();
// These get translated in flags important for user data management.
archPkg.defaultToDeviceProtectedStorage = String.valueOf(
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2e6006465bd9..9f4e86d527ce 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -99,6 +99,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
private static final int UPDATE_AVAILABLE = 1 << 2;
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
+ private static final int PERSISTENT = 1 << 4;
}
private int mBooleans;
@@ -217,6 +218,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
@Nullable
private String mAppMetadataFilePath;
+ private int mTargetSdkVersion;
+
+ @Nullable
+ private byte[] mRestrictUpdateHash;
+
/**
* Snapshot support.
*/
@@ -535,6 +541,24 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
onChanged();
}
+ public PackageSetting setIsPersistent(boolean isPersistent) {
+ setBoolean(Booleans.PERSISTENT, isPersistent);
+ onChanged();
+ return this;
+ }
+
+ public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
+ mTargetSdkVersion = targetSdkVersion;
+ onChanged();
+ return this;
+ }
+
+ public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) {
+ mRestrictUpdateHash = restrictUpdateHash;
+ onChanged();
+ return this;
+ }
+
@Override
public int getSharedUserAppId() {
return mSharedUserAppId;
@@ -701,6 +725,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
categoryOverride = other.categoryOverride;
mDomainSetId = other.mDomainSetId;
mAppMetadataFilePath = other.mAppMetadataFilePath;
+ mTargetSdkVersion = other.mTargetSdkVersion;
+ mRestrictUpdateHash = other.mRestrictUpdateHash == null
+ ? null : other.mRestrictUpdateHash.clone();
usesSdkLibraries = other.usesSdkLibraries != null
? Arrays.copyOf(other.usesSdkLibraries,
@@ -1572,6 +1599,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
}
+ @Override
+ public boolean isPersistent() {
+ return getBoolean(Booleans.PERSISTENT);
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1714,11 +1746,21 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return mAppMetadataFilePath;
}
+ @DataClass.Generated.Member
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable byte[] getRestrictUpdateHash() {
+ return mRestrictUpdateHash;
+ }
+
@DataClass.Generated(
- time = 1694196905013L,
+ time = 1696979728639L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index da14397b9c92..203e1de61f2f 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -517,12 +517,6 @@ final class ResolveIntentHelper {
if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
- // Only if the service query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
-
final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);
flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0cac790fd910..8d8acfd421de 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,8 @@ final class ScanPackageUtils {
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
- newDomainSetId);
+ newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
@@ -240,7 +241,8 @@ final class ScanPackageUtils {
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups(), newDomainSetId);
+ parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
}
if (createNewPackage && originalPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6e3b538c4849..451b3a5256ac 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1060,7 +1060,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1080,7 +1081,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
- .setDomainSetId(domainSetId);
+ .setDomainSetId(domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setFlags(pkgFlags)
.setPrivateFlags(pkgPrivateFlags);
} else {
@@ -1092,7 +1096,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
usesStaticLibraries, usesStaticLibrariesVersions,
- createMimeGroups(mimeGroupNames), domainSetId);
+ createMimeGroups(mimeGroupNames), domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setLastModifiedTime(codePath.lastModified());
if (sharedUser != null) {
pkgSetting.setSharedUserAppId(sharedUser.mAppId);
@@ -1206,7 +1213,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ int targetSdkVersion, byte[] restrictUpdatedHash)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
if (sharedUser != null) {
@@ -1258,7 +1266,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
.setSecondaryCpuAbi(secondaryCpuAbi)
.updateMimeGroups(mimeGroupNames)
- .setDomainSetId(domainSetId);
+ .setDomainSetId(domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
&& usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0e98158d7210..7331bc141ec2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1335,18 +1335,6 @@ public class UserManagerService extends IUserManager.Stub {
&& user.profileGroupId == profile.profileGroupId);
}
- private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode,
- boolean useManagedActions) {
- Intent intent = new Intent();
- intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions));
- intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
- intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle());
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier());
- intent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- return intent;
- }
-
private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {
return useManagedActions ?
enableQuietMode ?
@@ -1359,12 +1347,20 @@ public class UserManagerService extends IUserManager.Stub {
private void broadcastProfileAvailabilityChanges(UserInfo profileInfo,
UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) {
- Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode,
- useManagedActions);
+ Intent availabilityIntent = new Intent();
+ availabilityIntent.setAction(
+ getAvailabilityIntentAction(enableQuietMode, useManagedActions));
+ availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
+ availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle());
+ availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE,
+ profileInfo.getUserHandle().getIdentifier());
if (profileInfo.isManagedProfile()) {
getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
availabilityIntent, parentHandle, /* requiresPermission= */ true);
}
+ availabilityIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+
// TODO(b/302708423): Restrict the apps that can receive these intents in case of a private
// profile.
final Bundle options = new BroadcastOptions()
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d804e01aa31e..61e96ca3dd59 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -379,7 +379,7 @@ public class PackageInfoUtils {
ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
| flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
| flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
- if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0
+ if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0
&& state.isQuarantined()) {
ai.enabled = false;
} else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3f347e465f81..e7137bbd7969 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -434,4 +434,26 @@ public interface PackageState {
*/
@Nullable
String getApexModuleName();
+
+ /**
+ * @see ApplicationInfo#FLAG_PERSISTENT
+ * @see R.styleable#AndroidManifestApplication_persistent
+ * @hide
+ */
+ boolean isPersistent();
+
+ /**
+ * @see ApplicationInfo#targetSdkVersion
+ * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+ * @hide
+ */
+ int getTargetSdkVersion();
+
+ /**
+ * @see R.styleable#AndroidManifestRestrictUpdate
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ byte[] getRestrictUpdateHash();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index 7b07e5b2bb6b..cd3583b814a4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -16,9 +16,9 @@
package com.android.server.pm.pkg;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS;
import android.annotation.NonNull;
import android.content.pm.ComponentInfo;
@@ -147,7 +147,7 @@ public class PackageUserStateUtils {
return true;
}
- if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) {
+ if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 86391c91bea5..153238fa6866 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -17,7 +17,6 @@
package com.android.server.pm.pkg;
import android.annotation.Nullable;
-import android.content.pm.Flags;
import android.content.pm.SuspendDialogInfo;
import android.os.BaseBundle;
import android.os.PersistableBundle;
@@ -142,8 +141,7 @@ public final class SuspendParams {
PersistableBundle readAppExtras = null;
PersistableBundle readLauncherExtras = null;
- final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false)
- && Flags.quarantinedEnabled();
+ final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false);
final int currentDepth = in.getDepth();
int type;
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index 6cc9d0a54607..bc90f5c46130 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -18,15 +18,26 @@ package com.android.server.power.stats;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.os.BatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
-import java.io.PrintWriter;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -34,25 +45,75 @@ import java.util.Set;
* etc) covering a specific period of power usage history.
*/
class AggregatedPowerStats {
+ private static final String TAG = "AggregatedPowerStats";
+ private static final int MAX_CLOCK_UPDATES = 100;
+ private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
+
private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
- @CurrentTimeMillisLong
- private long mStartTime;
+ static class ClockUpdate {
+ public long monotonicTime;
+ @CurrentTimeMillisLong public long currentTime;
+ }
+
+ private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
@DurationMillisLong
private long mDurationMs;
- AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
- this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+ AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ List<AggregatedPowerStatsConfig.PowerComponent> configs =
+ aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
+ mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
+ for (int i = 0; i < configs.size(); i++) {
+ mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i));
+ }
+ }
+
+ private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats(
+ AggregatedPowerStatsConfig.PowerComponent config) {
+ switch (config.getPowerComponentId()) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return new CpuAggregatedPowerStats(config);
+ default:
+ return new PowerComponentAggregatedPowerStats(config);
+ }
+ }
+
+ /**
+ * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
+ * there may be multiple clock updates in one set of aggregated stats.
+ *
+ * @param monotonicTime monotonic time in milliseconds, see
+ * {@link com.android.internal.os.MonotonicClock}
+ * @param currentTime current time in milliseconds, see {@link System#currentTimeMillis()}
+ */
+ void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
+ ClockUpdate clockUpdate = new ClockUpdate();
+ clockUpdate.monotonicTime = monotonicTime;
+ clockUpdate.currentTime = currentTime;
+ if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
+ mClockUpdates.add(clockUpdate);
+ } else {
+ Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
+ + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
+ mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
+ }
}
- void setStartTime(@CurrentTimeMillisLong long startTime) {
- mStartTime = startTime;
+ /**
+ * Start time according to {@link com.android.internal.os.MonotonicClock}
+ */
+ long getStartTime() {
+ if (mClockUpdates.isEmpty()) {
+ return 0;
+ } else {
+ return mClockUpdates.get(0).monotonicTime;
+ }
}
- @CurrentTimeMillisLong
- public long getStartTime() {
- return mStartTime;
+ List<ClockUpdate> getClockUpdates() {
+ return mClockUpdates;
}
void setDuration(long durationMs) {
@@ -73,13 +134,14 @@ class AggregatedPowerStats {
return null;
}
- void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
+ long time) {
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.setState(stateId, state, time);
}
}
- void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long time) {
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.setUidState(uid, stateId, state, time);
@@ -106,20 +168,90 @@ class AggregatedPowerStats {
}
void reset() {
- mStartTime = 0;
+ mClockUpdates.clear();
mDurationMs = 0;
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.reset();
}
}
- void dump(PrintWriter pw) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.print("Start time: ");
- ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
- ipw.print(" duration: ");
- ipw.print(mDurationMs);
- ipw.println();
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.writeXml(serializer);
+ }
+ serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+ serializer.flush();
+ }
+
+ @NonNull
+ public static AggregatedPowerStats createFromXml(
+ TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
+ throws XmlPullParserException, IOException {
+ AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
+ boolean inElement = false;
+ boolean skipToEnd = false;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
+ if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_AGGREGATED_POWER_STATS:
+ inElement = true;
+ break;
+ case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT:
+ if (!inElement) {
+ break;
+ }
+
+ int powerComponentId = parser.getAttributeInt(null,
+ PowerComponentAggregatedPowerStats.XML_ATTR_ID);
+ for (PowerComponentAggregatedPowerStats powerComponent :
+ stats.mPowerComponentStats) {
+ if (powerComponent.powerComponentId == powerComponentId) {
+ if (!powerComponent.readFromXml(parser)) {
+ skipToEnd = true;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return stats;
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ StringBuilder sb = new StringBuilder();
+ long baseTime = 0;
+ for (int i = 0; i < mClockUpdates.size(); i++) {
+ ClockUpdate clockUpdate = mClockUpdates.get(i);
+ sb.setLength(0);
+ if (i == 0) {
+ baseTime = clockUpdate.monotonicTime;
+ sb.append("Start time: ")
+ .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime))
+ .append(" (")
+ .append(baseTime)
+ .append(") duration: ")
+ .append(mDurationMs);
+ ipw.println(sb);
+ } else {
+ sb.setLength(0);
+ sb.append("Clock update: ");
+ TimeUtils.formatDuration(
+ clockUpdate.monotonicTime - baseTime, sb,
+ TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
+ sb.append(" ").append(
+ DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime));
+ ipw.increaseIndent();
+ ipw.println(sb);
+ ipw.decreaseIndent();
+ }
+ }
ipw.println("Device");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
new file mode 100644
index 000000000000..477c2286abe0
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Configuration that controls how power stats are aggregated. It determines which state changes
+ * are to be considered as essential dimensions ("tracked states") for each power component (CPU,
+ * WiFi, etc). Also, it determines which states are tracked globally and which ones on a per-UID
+ * basis.
+ */
+public class AggregatedPowerStatsConfig {
+ public static final int STATE_POWER = 0;
+ public static final int STATE_SCREEN = 1;
+ public static final int STATE_PROCESS_STATE = 2;
+
+ @IntDef({
+ STATE_POWER,
+ STATE_SCREEN,
+ STATE_PROCESS_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrackedState {
+ }
+
+ static final String STATE_NAME_POWER = "pwr";
+ static final int POWER_STATE_BATTERY = 0;
+ static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
+ static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+ static final String STATE_NAME_SCREEN = "scr";
+ static final int SCREEN_STATE_ON = 0;
+ static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
+ static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+ static final String STATE_NAME_PROCESS_STATE = "ps";
+ static final String[] STATE_LABELS_PROCESS_STATE;
+
+ static {
+ String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+ procStateLabels[i] = BatteryConsumer.processStateToString(i);
+ }
+ STATE_LABELS_PROCESS_STATE = procStateLabels;
+ }
+
+ /**
+ * Configuration for a give power component (CPU, WiFi, etc)
+ */
+ public static class PowerComponent {
+ private final int mPowerComponentId;
+ private @TrackedState int[] mTrackedDeviceStates;
+ private @TrackedState int[] mTrackedUidStates;
+
+ PowerComponent(int powerComponentId) {
+ this.mPowerComponentId = powerComponentId;
+ }
+
+ /**
+ * Configures which states should be tracked as separate dimensions for the entire device.
+ */
+ public PowerComponent trackDeviceStates(@TrackedState int... states) {
+ mTrackedDeviceStates = states;
+ return this;
+ }
+
+ /**
+ * Configures which states should be tracked as separate dimensions on a per-UID basis.
+ */
+ public PowerComponent trackUidStates(@TrackedState int... states) {
+ mTrackedUidStates = states;
+ return this;
+ }
+
+ public int getPowerComponentId() {
+ return mPowerComponentId;
+ }
+
+ public MultiStateStats.States[] getDeviceStateConfig() {
+ return new MultiStateStats.States[]{
+ new MultiStateStats.States(STATE_NAME_POWER,
+ isTracked(mTrackedDeviceStates, STATE_POWER),
+ STATE_LABELS_POWER),
+ new MultiStateStats.States(STATE_NAME_SCREEN,
+ isTracked(mTrackedDeviceStates, STATE_SCREEN),
+ STATE_LABELS_SCREEN),
+ };
+ }
+
+ public MultiStateStats.States[] getUidStateConfig() {
+ return new MultiStateStats.States[]{
+ new MultiStateStats.States(STATE_NAME_POWER,
+ isTracked(mTrackedUidStates, STATE_POWER),
+ AggregatedPowerStatsConfig.STATE_LABELS_POWER),
+ new MultiStateStats.States(STATE_NAME_SCREEN,
+ isTracked(mTrackedUidStates, STATE_SCREEN),
+ AggregatedPowerStatsConfig.STATE_LABELS_SCREEN),
+ new MultiStateStats.States(STATE_NAME_PROCESS_STATE,
+ isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+ AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE),
+ };
+ }
+
+ private boolean isTracked(int[] trackedStates, int state) {
+ if (trackedStates == null) {
+ return false;
+ }
+
+ for (int trackedState : trackedStates) {
+ if (trackedState == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final List<PowerComponent> mPowerComponents = new ArrayList<>();
+
+ /**
+ * Creates a configuration for the specified power component, which may be one of the
+ * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
+ * a custom power component.
+ */
+ public PowerComponent trackPowerComponent(int powerComponentId) {
+ PowerComponent builder = new PowerComponent(powerComponentId);
+ mPowerComponents.add(builder);
+ return builder;
+ }
+
+ public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
+ return mPowerComponents;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
new file mode 100644
index 000000000000..7ba433017413
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "aggregated-power-stats";
+
+ private final AggregatedPowerStats mAggregatedPowerStats;
+
+ AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
+ super(TYPE);
+ mAggregatedPowerStats = aggregatedPowerStats;
+ }
+
+ public AggregatedPowerStats getAggregatedPowerStats() {
+ return mAggregatedPowerStats;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ mAggregatedPowerStats.writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mAggregatedPowerStats.dump(ipw);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 613f18982b86..a9c2bc28b7a0 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -124,6 +124,7 @@ import com.android.internal.os.KernelMemoryBandwidthStats;
import com.android.internal.os.KernelSingleUidTimeReader;
import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.os.LongMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.internal.os.RailStats;
@@ -283,7 +284,6 @@ public class BatteryStatsImpl extends BatteryStats {
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
- private final PowerStatsAggregator mPowerStatsAggregator;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -356,6 +356,11 @@ public class BatteryStatsImpl extends BatteryStats {
protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
@NonNull
+ public BatteryStatsHistory getHistory() {
+ return mHistory;
+ }
+
+ @NonNull
BatteryStatsHistory copyHistory() {
return mHistory.copy();
}
@@ -1718,14 +1723,7 @@ public class BatteryStatsImpl extends BatteryStats {
return mMaxLearnedBatteryCapacityUah;
}
- public BatteryStatsImpl() {
- this(Clock.SYSTEM_CLOCK);
- }
-
- public BatteryStatsImpl(Clock clock) {
- this(clock, null);
- }
-
+ @VisibleForTesting
public BatteryStatsImpl(Clock clock, File historyDirectory) {
init(clock);
mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
@@ -1737,18 +1735,19 @@ public class BatteryStatsImpl extends BatteryStats {
mCheckinFile = null;
mStatsFile = null;
mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+ new MonotonicClock(0, mClock));
} else {
mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+ new MonotonicClock(0, mClock));
}
mPlatformIdleStateCallback = null;
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
mCpuPowerStatsCollector = null;
- mPowerStatsAggregator = null;
}
private void init(Clock clock) {
@@ -10906,20 +10905,12 @@ public class BatteryStatsImpl extends BatteryStats {
return mTmpCpuTimeInFreq;
}
- public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir,
+ public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
+ @NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
@NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
@Nullable EnergyStatsRetriever energyStatsCb,
@NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
@NonNull CpuScalingPolicies cpuScalingPolicies) {
- this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider,
- powerProfile, cpuScalingPolicies);
- }
-
- private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
- @Nullable File systemDir, @NonNull Handler handler,
- @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb,
- @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
- @NonNull CpuScalingPolicies cpuScalingPolicies) {
init(clock);
mBatteryStatsConfig = config;
@@ -10936,31 +10927,19 @@ public class BatteryStatsImpl extends BatteryStats {
mCheckinFile = null;
mDailyFile = null;
mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
}
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
- PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
- builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
- .trackDeviceStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN)
- .trackUidStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN,
- PowerStatsAggregator.STATE_PROCESS_STATE);
-
- mPowerStatsAggregator = builder.build();
-
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -15702,18 +15681,21 @@ public class BatteryStatsImpl extends BatteryStats {
}
/**
- * Grabs one sample of PowerStats and prints it.
+ * Schedules an immediate (but asynchronous) collection of PowerStats samples.
+ * Callers will need to wait for the collection to complete on the handler thread.
*/
- public void dumpStatsSample(PrintWriter pw) {
- mCpuPowerStatsCollector.collectAndDump(pw);
+ public void schedulePowerStatsSampleCollection() {
+ if (mCpuPowerStatsCollector == null) {
+ return;
+ }
+ mCpuPowerStatsCollector.forceSchedule();
}
/**
- * Aggregates power stats between the specified times and prints them.
+ * Grabs one sample of PowerStats and prints it.
*/
- public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
- mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
- stats-> stats.dump(pw));
+ public void dumpStatsSample(PrintWriter pw) {
+ mCpuPowerStatsCollector.collectAndDump(pw);
}
private final Runnable mWriteAsyncRunnable = () -> {
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index f6fa9f244252..851a3f7bdaba 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -46,7 +46,7 @@ public class BatteryUsageStatsProvider {
private static final String TAG = "BatteryUsageStatsProv";
private final Context mContext;
private final BatteryStats mStats;
- private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+ private final PowerStatsStore mPowerStatsStore;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
private final Object mLock = new Object();
@@ -58,10 +58,10 @@ public class BatteryUsageStatsProvider {
@VisibleForTesting
public BatteryUsageStatsProvider(Context context, BatteryStats stats,
- BatteryUsageStatsStore batteryUsageStatsStore) {
+ PowerStatsStore powerStatsStore) {
mContext = context;
mStats = stats;
- mBatteryUsageStatsStore = batteryUsageStatsStore;
+ mPowerStatsStore = powerStatsStore;
mPowerProfile = stats instanceof BatteryStatsImpl
? ((BatteryStatsImpl) stats).getPowerProfile()
: new PowerProfile(context);
@@ -314,20 +314,52 @@ public class BatteryUsageStatsProvider {
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
customEnergyConsumerNames, includePowerModels, includeProcessStateData,
minConsumedPowerThreshold);
- if (mBatteryUsageStatsStore == null) {
- Log.e(TAG, "BatteryUsageStatsStore is unavailable");
+ if (mPowerStatsStore == null) {
+ Log.e(TAG, "PowerStatsStore is unavailable");
return builder.build();
}
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- for (long timestamp : timestamps) {
- if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) {
- final BatteryUsageStats snapshot =
- mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
- if (snapshot == null) {
- continue;
- }
+ List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+ for (PowerStatsSpan.Metadata spanMetadata : toc) {
+ if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) {
+ continue;
+ }
+
+ // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the
+ // session end time.
+ //
+ // The following algorithm is correct when there is only one time frame in the span.
+ // When the wall-clock time is adjusted in the middle of an stats span,
+ // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm
+ // only covers some situations, but not others. When using the resulting data for
+ // analysis, we should always pay attention to the full set of included timeframes.
+ // TODO(b/298459065): switch to monotonic clock
+ long minTime = Long.MAX_VALUE;
+ long maxTime = 0;
+ for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) {
+ long spanEndTime = timeFrame.startTime + timeFrame.duration;
+ minTime = Math.min(minTime, spanEndTime);
+ maxTime = Math.max(maxTime, spanEndTime);
+ }
+
+ // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
+ // while the "to" timestamp is *inclusive*.
+ boolean isInRange =
+ (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
+ && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+ if (!isInRange) {
+ continue;
+ }
+
+ PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
+ if (powerStatsSpan == null) {
+ continue;
+ }
+ for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+ BatteryUsageStats snapshot =
+ ((BatteryUsageStatsSection) section).getBatteryUsageStats();
if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
customEnergyConsumerNames)) {
Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
new file mode 100644
index 000000000000..b95faac7c111
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class BatteryUsageStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "battery-usage-stats";
+
+ private final BatteryUsageStats mBatteryUsageStats;
+
+ BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) {
+ super(TYPE);
+ mBatteryUsageStats = batteryUsageStats;
+ }
+
+ public BatteryUsageStats getBatteryUsageStats() {
+ return mBatteryUsageStats;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ mBatteryUsageStats.writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mBatteryUsageStats.dump(ipw, "");
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
deleted file mode 100644
index 0d7a1406f751..000000000000
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.power.stats;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.LongArray;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A storage mechanism for BatteryUsageStats snapshots.
- */
-public class BatteryUsageStatsStore {
- private static final String TAG = "BatteryUsageStatsStore";
-
- private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of(
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .includeProcessStateData()
- .build());
- private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats";
- private static final String SNAPSHOT_FILE_EXTENSION = ".bus";
- private static final String DIR_LOCK_FILENAME = ".lock";
- private static final String CONFIG_FILENAME = "config";
- private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
- "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
- private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024;
-
- private final Context mContext;
- private final BatteryStatsImpl mBatteryStats;
- private boolean mSystemReady;
- private final File mStoreDir;
- private final File mLockFile;
- private final ReentrantLock mFileLock = new ReentrantLock();
- private FileLock mJvmLock;
- private final AtomicFile mConfigFile;
- private final long mMaxStorageBytes;
- private final Handler mHandler;
- private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-
- public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir,
- Handler handler) {
- this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- }
-
- @VisibleForTesting
- public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir,
- Handler handler, long maxStorageBytes) {
- mContext = context;
- mBatteryStats = batteryStats;
- mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR);
- mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
- mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME));
- mHandler = handler;
- mMaxStorageBytes = maxStorageBytes;
- mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset);
- mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats);
- }
-
- /**
- * Notifies BatteryUsageStatsStore that the system server is ready.
- */
- public void onSystemReady() {
- mSystemReady = true;
- }
-
- private void prepareForBatteryStatsReset(int resetReason) {
- if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) {
- return;
- }
-
- final List<BatteryUsageStats> stats =
- mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY);
- if (stats.isEmpty()) {
- Slog.wtf(TAG, "No battery usage stats generated");
- return;
- }
-
- mHandler.post(() -> storeBatteryUsageStats(stats.get(0)));
- }
-
- private void storeBatteryUsageStats(BatteryUsageStats stats) {
- lockSnapshotDirectory();
- try {
- if (!mStoreDir.exists()) {
- if (!mStoreDir.mkdirs()) {
- Slog.e(TAG, "Could not create a directory for battery usage stats snapshots");
- return;
- }
- }
- File file = makeSnapshotFilename(stats.getStatsEndTimestamp());
- try {
- writeXmlFileLocked(stats, file);
- } catch (Exception e) {
- Slog.e(TAG, "Cannot save battery usage stats", e);
- }
-
- removeOldSnapshotsLocked();
- } finally {
- unlockSnapshotDirectory();
- }
- }
-
- /**
- * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds
- * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}.
- */
- public long[] listBatteryUsageStatsTimestamps() {
- LongArray timestamps = new LongArray(100);
- lockSnapshotDirectory();
- try {
- for (File file : mStoreDir.listFiles()) {
- String fileName = file.getName();
- if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) {
- try {
- String fileNameWithoutExtension = fileName.substring(0,
- fileName.length() - SNAPSHOT_FILE_EXTENSION.length());
- timestamps.add(Long.parseLong(fileNameWithoutExtension));
- } catch (NumberFormatException e) {
- Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: "
- + fileName);
- }
- }
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return timestamps.toArray();
- }
-
- /**
- * Reads the specified snapshot of BatteryUsageStats. Returns null if the snapshot
- * does not exist.
- */
- @Nullable
- public BatteryUsageStats loadBatteryUsageStats(long timestamp) {
- lockSnapshotDirectory();
- try {
- File file = makeSnapshotFilename(timestamp);
- try {
- return readXmlFileLocked(file);
- } catch (Exception e) {
- Slog.e(TAG, "Cannot read battery usage stats", e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return null;
- }
-
- /**
- * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
- * in persistent file.
- */
- public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
- Properties props = new Properties();
- lockSnapshotDirectory();
- try {
- try (InputStream in = mConfigFile.openRead()) {
- props.load(in);
- } catch (IOException e) {
- Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
- }
- props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
- String.valueOf(timestamp));
- FileOutputStream out = null;
- try {
- out = mConfigFile.startWrite();
- props.store(out, "Statsd atom pull timestamps");
- mConfigFile.finishWrite(out);
- } catch (IOException e) {
- mConfigFile.failWrite(out);
- Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- }
-
- /**
- * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
- * statsd atom pull.
- */
- public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
- Properties props = new Properties();
- lockSnapshotDirectory();
- try {
- try (InputStream in = mConfigFile.openRead()) {
- props.load(in);
- } catch (IOException e) {
- Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return Long.parseLong(
- props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
- }
-
- private void lockSnapshotDirectory() {
- mFileLock.lock();
-
- // Lock the directory from access by other JVMs
- try {
- mLockFile.getParentFile().mkdirs();
- mLockFile.createNewFile();
- mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
- } catch (IOException e) {
- Log.e(TAG, "Cannot lock snapshot directory", e);
- }
- }
-
- private void unlockSnapshotDirectory() {
- try {
- mJvmLock.close();
- } catch (IOException e) {
- Log.e(TAG, "Cannot unlock snapshot directory", e);
- } finally {
- mFileLock.unlock();
- }
- }
-
- /**
- * Creates a file name by formatting the timestamp as 19-digit zero-padded number.
- * This ensures that sorted directory list follows the chronological order.
- */
- private File makeSnapshotFilename(long statsEndTimestamp) {
- return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp)
- + SNAPSHOT_FILE_EXTENSION);
- }
-
- private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException {
- try (OutputStream out = new FileOutputStream(file)) {
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- stats.writeXml(serializer);
- serializer.endDocument();
- }
- }
-
- private BatteryUsageStats readXmlFileLocked(File file)
- throws IOException, XmlPullParserException {
- try (InputStream in = new FileInputStream(file)) {
- TypedXmlPullParser parser = Xml.newBinaryPullParser();
- parser.setInput(in, StandardCharsets.UTF_8.name());
- return BatteryUsageStats.createFromXml(parser);
- }
- }
-
- private void removeOldSnapshotsLocked() {
- // Read the directory list into a _sorted_ map. The alphanumeric ordering
- // corresponds to the historical order of snapshots because the file names
- // are timestamps zero-padded to the same length.
- long totalSize = 0;
- TreeMap<File, Long> mFileSizes = new TreeMap<>();
- for (File file : mStoreDir.listFiles()) {
- final long fileSize = file.length();
- totalSize += fileSize;
- if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
- mFileSizes.put(file, fileSize);
- }
- }
-
- while (totalSize > mMaxStorageBytes) {
- final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
- if (entry == null) {
- break;
- }
-
- File file = entry.getKey();
- if (!file.delete()) {
- Slog.e(TAG, "Cannot delete battery usage stats " + file);
- }
- totalSize -= entry.getValue();
- mFileSizes.remove(file);
- }
- }
-
- public void removeAllSnapshots() {
- lockSnapshotDirectory();
- try {
- for (File file : mStoreDir.listFiles()) {
- if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
- if (!file.delete()) {
- Slog.e(TAG, "Cannot delete battery usage stats " + file);
- }
- }
- }
- } finally {
- unlockSnapshotDirectory();
- }
- }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
index 5b3fe064d79a..fbf692800e52 100644
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -16,14 +16,8 @@
package com.android.server.power.stats;
-import android.os.BatteryConsumer;
-
-import com.android.internal.os.MultiStateStats;
-
class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
-
- CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
- MultiStateStats.States[] uidStates) {
- super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+ CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ super(config);
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 686268fea22b..05c0a13642b4 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,13 @@ import android.util.SparseArray;
import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.Collection;
/**
@@ -31,6 +37,12 @@ import java.util.Collection;
* as part of the {@link PowerStats.Descriptor}.
*/
class PowerComponentAggregatedPowerStats {
+ static final String XML_TAG_POWER_COMPONENT = "power_component";
+ static final String XML_ATTR_ID = "id";
+ private static final String XML_TAG_DEVICE_STATS = "device-stats";
+ private static final String XML_TAG_UID_STATS = "uid-stats";
+ private static final String XML_ATTR_UID = "uid";
+
public final int powerComponentId;
private final MultiStateStats.States[] mDeviceStateConfig;
private final MultiStateStats.States[] mUidStateConfig;
@@ -49,22 +61,24 @@ class PowerComponentAggregatedPowerStats {
public MultiStateStats stats;
}
- PowerComponentAggregatedPowerStats(int powerComponentId,
- MultiStateStats.States[] deviceStates,
- MultiStateStats.States[] uidStates) {
- this.powerComponentId = powerComponentId;
- mDeviceStateConfig = deviceStates;
- mUidStateConfig = uidStates;
+ PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ this.powerComponentId = config.getPowerComponentId();
+ mDeviceStateConfig = config.getDeviceStateConfig();
+ mUidStateConfig = config.getUidStateConfig();
mDeviceStates = new int[mDeviceStateConfig.length];
mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
}
- void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ public PowerStats.Descriptor getPowerStatsDescriptor() {
+ return mPowerStatsDescriptor;
+ }
+
+ void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
mDeviceStates[stateId] = state;
mDeviceStateTimestamps[stateId] = time;
if (mDeviceStateConfig[stateId].isTracked()) {
- if (mDeviceStats != null || createDeviceStats()) {
+ if (mDeviceStats != null) {
mDeviceStats.setState(stateId, state, time);
}
}
@@ -72,14 +86,14 @@ class PowerComponentAggregatedPowerStats {
if (mUidStateConfig[stateId].isTracked()) {
for (int i = mUidStats.size() - 1; i >= 0; i--) {
PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
- if (uidStats.stats != null || createUidStats(uidStats)) {
+ if (uidStats.stats != null) {
uidStats.stats.setState(stateId, state, time);
}
}
}
}
- void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long time) {
if (!mUidStateConfig[stateId].isTracked()) {
return;
@@ -89,7 +103,7 @@ class PowerComponentAggregatedPowerStats {
uidStats.states[stateId] = state;
uidStats.stateTimestampMs[stateId] = time;
- if (uidStats.stats != null || createUidStats(uidStats)) {
+ if (uidStats.stats != null) {
uidStats.stats.setState(stateId, state, time);
}
}
@@ -102,13 +116,6 @@ class PowerComponentAggregatedPowerStats {
mPowerStatsDescriptor = powerStats.descriptor;
if (mDeviceStats == null) {
- if (mStatsFactory == null) {
- mStatsFactory = new MultiStateStats.Factory(
- mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
- mUidStatsFactory = new MultiStateStats.Factory(
- mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
- }
-
createDeviceStats();
}
@@ -183,7 +190,11 @@ class PowerComponentAggregatedPowerStats {
private boolean createDeviceStats() {
if (mStatsFactory == null) {
- return false;
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ mStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
}
mDeviceStats = mStatsFactory.create();
@@ -196,7 +207,11 @@ class PowerComponentAggregatedPowerStats {
private boolean createUidStats(UidStats uidStats) {
if (mUidStatsFactory == null) {
- return false;
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ mUidStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
}
uidStats.stats = mUidStatsFactory.create();
@@ -211,6 +226,74 @@ class PowerComponentAggregatedPowerStats {
return true;
}
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ // No stats aggregated - can skip writing XML altogether
+ if (mPowerStatsDescriptor == null) {
+ return;
+ }
+
+ serializer.startTag(null, XML_TAG_POWER_COMPONENT);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ mPowerStatsDescriptor.writeXml(serializer);
+
+ if (mDeviceStats != null) {
+ serializer.startTag(null, XML_TAG_DEVICE_STATS);
+ mDeviceStats.writeXml(serializer);
+ serializer.endTag(null, XML_TAG_DEVICE_STATS);
+ }
+
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ int uid = mUidStats.keyAt(i);
+ UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null) {
+ serializer.startTag(null, XML_TAG_UID_STATS);
+ serializer.attributeInt(null, XML_ATTR_UID, uid);
+ uidStats.stats.writeXml(serializer);
+ serializer.endTag(null, XML_TAG_UID_STATS);
+ }
+ }
+
+ serializer.endTag(null, XML_TAG_POWER_COMPONENT);
+ serializer.flush();
+ }
+
+ public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case PowerStats.Descriptor.XML_TAG_DESCRIPTOR:
+ mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser);
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ break;
+ case XML_TAG_DEVICE_STATS:
+ if (mDeviceStats == null) {
+ createDeviceStats();
+ }
+ if (!mDeviceStats.readFromXml(parser)) {
+ return false;
+ }
+ break;
+ case XML_TAG_UID_STATS:
+ int uid = parser.getAttributeInt(null, XML_ATTR_UID);
+ UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats);
+ }
+ if (!uidStats.stats.readFromXml(parser)) {
+ return false;
+ }
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return true;
+ }
+
void dumpDevice(IndentingPrintWriter ipw) {
if (mDeviceStats != null) {
ipw.println(mPowerStatsDescriptor.name);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 6a1c1da93163..f374fb77cae8 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -15,59 +15,26 @@
*/
package com.android.server.power.stats;
-import android.annotation.IntDef;
-import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
-import com.android.internal.os.MultiStateStats;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
import java.util.function.Consumer;
-class PowerStatsAggregator {
- public static final int STATE_POWER = 0;
- public static final int STATE_SCREEN = 1;
- public static final int STATE_PROCESS_STATE = 2;
-
- @IntDef({
- STATE_POWER,
- STATE_SCREEN,
- STATE_PROCESS_STATE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrackedState {
- }
-
- static final int POWER_STATE_BATTERY = 0;
- static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
- static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
-
- static final int SCREEN_STATE_ON = 0;
- static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
- static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
-
- static final String[] STATE_LABELS_PROCESS_STATE;
-
- static {
- String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
- for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
- procStateLabels[i] = BatteryConsumer.processStateToString(i);
- }
- STATE_LABELS_PROCESS_STATE = procStateLabels;
- }
-
- private final BatteryStatsHistory mHistory;
+/**
+ * Power stats aggregator. It reads through portions of battery stats history, finds
+ * relevant items (state changes, power stats etc) and produces one or more
+ * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
+ */
+public class PowerStatsAggregator {
private final AggregatedPowerStats mStats;
+ private final BatteryStatsHistory mHistory;
- private PowerStatsAggregator(BatteryStatsHistory history,
- AggregatedPowerStats aggregatedPowerStats) {
+ public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
+ BatteryStatsHistory history) {
+ mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
mHistory = history;
- mStats = aggregatedPowerStats;
}
/**
@@ -82,12 +49,10 @@ class PowerStatsAggregator {
* Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
* the stats in the <code>accept</code> method and never cache it.
*/
- void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+ public void aggregatePowerStats(long startTimeMs, long endTimeMs,
Consumer<AggregatedPowerStats> consumer) {
- mStats.reset();
-
- int currentBatteryState = POWER_STATE_BATTERY;
- int currentScreenState = SCREEN_STATE_OTHER;
+ int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+ int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
long baseTime = -1;
long lastTime = 0;
try (BatteryStatsHistoryIterator iterator =
@@ -96,131 +61,60 @@ class PowerStatsAggregator {
BatteryStats.HistoryItem item = iterator.next();
if (baseTime < 0) {
- mStats.setStartTime(item.currentTime);
+ mStats.addClockUpdate(item.time, item.currentTime);
baseTime = item.time;
+ } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
+ mStats.addClockUpdate(item.time, item.currentTime);
}
lastTime = item.time;
int batteryState =
(item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
- ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+ ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
+ : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
if (batteryState != currentBatteryState) {
- mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+ mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
+ item.time);
currentBatteryState = batteryState;
}
int screenState =
(item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
- ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+ ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
+ : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
if (screenState != currentScreenState) {
- mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+ mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
+ item.time);
currentScreenState = screenState;
}
if (item.processStateChange != null) {
- mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+ mStats.setUidState(item.processStateChange.uid,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
item.processStateChange.processState, item.time);
}
if (item.powerStats != null) {
if (!mStats.isCompatible(item.powerStats)) {
- mStats.setDuration(lastTime - baseTime);
- consumer.accept(mStats);
+ if (lastTime > baseTime) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ }
mStats.reset();
- mStats.setStartTime(item.currentTime);
+ mStats.addClockUpdate(item.time, item.currentTime);
baseTime = lastTime = item.time;
}
mStats.addPowerStats(item.powerStats, item.time);
}
}
}
- mStats.setDuration(lastTime - baseTime);
- consumer.accept(mStats);
- }
-
- static class Builder {
- static class PowerComponentAggregateStatsBuilder {
- private final int mPowerComponentId;
- private @TrackedState int[] mTrackedDeviceStates;
- private @TrackedState int[] mTrackedUidStates;
-
- PowerComponentAggregateStatsBuilder(int powerComponentId) {
- this.mPowerComponentId = powerComponentId;
- }
-
- public PowerComponentAggregateStatsBuilder trackDeviceStates(
- @TrackedState int... states) {
- mTrackedDeviceStates = states;
- return this;
- }
-
- public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
- mTrackedUidStates = states;
- return this;
- }
-
- private PowerComponentAggregatedPowerStats build() {
- MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
- new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
- PowerStatsAggregator.STATE_LABELS_POWER),
- new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
- PowerStatsAggregator.STATE_LABELS_SCREEN),
- };
-
- MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
- new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
- PowerStatsAggregator.STATE_LABELS_POWER),
- new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
- PowerStatsAggregator.STATE_LABELS_SCREEN),
- new MultiStateStats.States(
- isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
- PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
- };
-
- switch (mPowerComponentId) {
- case BatteryConsumer.POWER_COMPONENT_CPU:
- return new CpuAggregatedPowerStats(deviceStates, uidStates);
- default:
- return new PowerComponentAggregatedPowerStats(mPowerComponentId,
- deviceStates, uidStates);
- }
- }
-
- private boolean isTracked(int[] trackedStates, int state) {
- if (trackedStates == null) {
- return false;
- }
-
- for (int trackedState : trackedStates) {
- if (trackedState == state) {
- return true;
- }
- }
- return false;
- }
- }
-
- private final BatteryStatsHistory mHistory;
- private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
- new ArrayList<>();
-
- Builder(BatteryStatsHistory history) {
- mHistory = history;
- }
-
- PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
- PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
- powerComponentId);
- mPowerComponents.add(builder);
- return builder;
+ if (lastTime > baseTime) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
}
- PowerStatsAggregator build() {
- return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
- mPowerComponents.stream()
- .map(PowerComponentAggregateStatsBuilder::build)
- .toArray(PowerComponentAggregatedPowerStats[]::new)));
- }
+ mStats.reset(); // to free up memory
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
new file mode 100644
index 000000000000..58619c70e0af
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.power.stats;
+
+import android.annotation.DurationMillisLong;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
+ * {@link PowerStatsStore}.
+ */
+public class PowerStatsScheduler {
+ private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
+ private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+ private final Context mContext;
+ private boolean mEnablePeriodicPowerStatsCollection;
+ @DurationMillisLong
+ private final long mAggregatedPowerStatsSpanDuration;
+ @DurationMillisLong
+ private final long mPowerStatsAggregationPeriod;
+ private final PowerStatsStore mPowerStatsStore;
+ private final Clock mClock;
+ private final MonotonicClock mMonotonicClock;
+ private final Handler mHandler;
+ private final BatteryStatsImpl mBatteryStats;
+ private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private final PowerStatsAggregator mPowerStatsAggregator;
+ private long mLastSavedSpanEndMonotonicTime;
+
+ public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+ @DurationMillisLong long aggregatedPowerStatsSpanDuration,
+ @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
+ Clock clock, MonotonicClock monotonicClock, Handler handler,
+ BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) {
+ mContext = context;
+ mPowerStatsAggregator = powerStatsAggregator;
+ mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
+ mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
+ mPowerStatsStore = powerStatsStore;
+ mClock = clock;
+ mMonotonicClock = monotonicClock;
+ mHandler = handler;
+ mBatteryStats = batteryStats;
+ mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+ }
+
+ /**
+ * Kicks off the scheduling of power stats aggregation spans.
+ */
+ public void start(boolean enablePeriodicPowerStatsCollection) {
+ mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset);
+ mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
+ if (mEnablePeriodicPowerStatsCollection) {
+ scheduleNextPowerStatsAggregation();
+ }
+ }
+
+ private void scheduleNextPowerStatsAggregation() {
+ AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME,
+ mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+ () -> {
+ schedulePowerStatsAggregation();
+ mHandler.post(this::scheduleNextPowerStatsAggregation);
+ }, mHandler);
+ }
+
+ /**
+ * Initiate an asynchronous process of aggregation of power stats.
+ */
+ @VisibleForTesting
+ public void schedulePowerStatsAggregation() {
+ // Catch up the power stats collectors
+ mBatteryStats.schedulePowerStatsSampleCollection();
+ mHandler.post(this::aggregateAndStorePowerStats);
+ }
+
+ private void aggregateAndStorePowerStats() {
+ long currentTimeMillis = mClock.currentTimeMillis();
+ long currentMonotonicTime = mMonotonicClock.monotonicTime();
+ long startTime = getLastSavedSpanEndMonotonicTime();
+ long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
+ mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
+ while (endTimeMs <= currentMonotonicTime) {
+ mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
+ stats -> {
+ storeAggregatedPowerStats(stats);
+ mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
+ });
+
+ startTime = endTimeMs;
+ endTimeMs += mAggregatedPowerStatsSpanDuration;
+ }
+ }
+
+ /**
+ * Performs a power stats aggregation pass and then dumps all stored aggregated power stats
+ * spans followed by the remainder that has not been stored yet.
+ */
+ public void aggregateAndDumpPowerStats(PrintWriter pw) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("Should not be executed on the bg handler thread.");
+ }
+
+ schedulePowerStatsAggregation();
+
+ // Wait for the aggregation process to finish storing aggregated stats spans in the store.
+ awaitCompletion();
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ mHandler.post(() -> {
+ mPowerStatsStore.dump(ipw);
+ // Aggregate the remainder of power stats and dump the results without storing them yet.
+ long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
+ mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0,
+ stats -> {
+ // Create a PowerStatsSpan for consistency of the textual output
+ PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
+ if (span != null) {
+ span.dump(ipw);
+ }
+ });
+ });
+
+ awaitCompletion();
+ }
+
+ /**
+ * Align the supplied time to the wall clock, for aesthetic purposes. For example, if
+ * the schedule is configured with a 15-min interval, the captured aggregated stats will
+ * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current
+ * time is used for the alignment, so if the wall clock changed during an aggregation span,
+ * or if the device was off (which stops the monotonic clock), the alignment may be
+ * temporarily broken.
+ */
+ @VisibleForTesting
+ public static long alignToWallClock(long targetMonotonicTime, long interval,
+ long currentMonotonicTime, long currentTimeMillis) {
+
+ // Estimate the wall clock time for the requested targetMonotonicTime
+ long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime);
+
+ if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) {
+ // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc
+
+ // First, round up to the next whole minute
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ // Now set the minute to a multiple of the requested interval
+ int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS);
+ cal.set(Calendar.MINUTE,
+ ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes)
+ * intervalInMinutes);
+
+ long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+ return targetMonotonicTime + adjustment;
+ } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) {
+ // If the interval is a divisor of a day, e.g. 2h, 3h, etc
+
+ // First, round up to the next whole hour
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ // Now set the hour of day to a multiple of the requested interval
+ int intervalInHours = (int) (interval / HOUR_IN_MILLIS);
+ cal.set(Calendar.HOUR_OF_DAY,
+ ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours)
+ * intervalInHours);
+
+ long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+ return targetMonotonicTime + adjustment;
+ }
+
+ return targetMonotonicTime;
+ }
+
+ private long getLastSavedSpanEndMonotonicTime() {
+ if (mLastSavedSpanEndMonotonicTime != 0) {
+ return mLastSavedSpanEndMonotonicTime;
+ }
+
+ for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
+ if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+ for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
+ long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
+ if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
+ mLastSavedSpanEndMonotonicTime = endMonotonicTime;
+ }
+ }
+ }
+ }
+ return mLastSavedSpanEndMonotonicTime;
+ }
+
+ private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+ mPowerStatsStore.storeAggregatedPowerStats(stats);
+ }
+
+ private void storeBatteryUsageStatsOnReset(int resetReason) {
+ if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
+ return;
+ }
+
+ final BatteryUsageStats batteryUsageStats =
+ mBatteryUsageStatsProvider.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includePowerModels()
+ .includeProcessStateData()
+ .build());
+
+ // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+ // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+ // start time
+ long monotonicStartTime =
+ mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+ mHandler.post(() ->
+ mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
+ }
+
+ private void awaitCompletion() {
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
new file mode 100644
index 000000000000..3b260ca0c3b9
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import com.google.android.collect.Sets;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Contains power stats of various kinds, aggregated over a time span.
+ */
+public class PowerStatsSpan {
+ private static final String TAG = "PowerStatsStore";
+
+ /**
+ * Increment VERSION when the XML format of the store changes. Also, update
+ * {@link #isCompatibleXmlFormat} to return true for all legacy versions
+ * that are compatible with the new one.
+ */
+ private static final int VERSION = 1;
+
+ private static final String XML_TAG_METADATA = "metadata";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_VERSION = "version";
+ private static final String XML_TAG_TIMEFRAME = "timeframe";
+ private static final String XML_ATTR_MONOTONIC = "monotonic";
+ private static final String XML_ATTR_START_TIME = "start";
+ private static final String XML_ATTR_DURATION = "duration";
+ private static final String XML_TAG_SECTION = "section";
+ private static final String XML_ATTR_SECTION_TYPE = "type";
+
+ private static final DateTimeFormatter DATE_FORMAT =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+ static class TimeFrame {
+ public final long startMonotonicTime;
+ @CurrentTimeMillisLong
+ public final long startTime;
+ @DurationMillisLong
+ public final long duration;
+
+ TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime,
+ @DurationMillisLong long duration) {
+ this.startMonotonicTime = startMonotonicTime;
+ this.startTime = startTime;
+ this.duration = duration;
+ }
+
+ void write(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_TIMEFRAME);
+ serializer.attributeLong(null, XML_ATTR_START_TIME, startTime);
+ serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime);
+ serializer.attributeLong(null, XML_ATTR_DURATION, duration);
+ serializer.endTag(null, XML_TAG_TIMEFRAME);
+ }
+
+ static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException {
+ return new TimeFrame(
+ parser.getAttributeLong(null, XML_ATTR_MONOTONIC),
+ parser.getAttributeLong(null, XML_ATTR_START_TIME),
+ parser.getAttributeLong(null, XML_ATTR_DURATION));
+ }
+
+ /**
+ * Prints the contents of this TimeFrame.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime)))
+ .append(" (monotonic=").append(startMonotonicTime).append(") ")
+ .append(" duration=");
+ String durationString = TimeUtils.formatDuration(duration);
+ if (durationString.startsWith("+")) {
+ sb.append(durationString.substring(1));
+ } else {
+ sb.append(durationString);
+ }
+ pw.print(sb);
+ }
+ }
+
+ static class Metadata {
+ static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId);
+
+ private final long mId;
+ private final List<TimeFrame> mTimeFrames = new ArrayList<>();
+ private final List<String> mSections = new ArrayList<>();
+
+ Metadata(long id) {
+ mId = id;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public List<TimeFrame> getTimeFrames() {
+ return mTimeFrames;
+ }
+
+ public List<String> getSections() {
+ return mSections;
+ }
+
+ void addTimeFrame(TimeFrame timeFrame) {
+ mTimeFrames.add(timeFrame);
+ }
+
+ void addSection(String sectionType) {
+ // The number of sections per span is small, so there is no need to use a Set
+ if (!mSections.contains(sectionType)) {
+ mSections.add(sectionType);
+ }
+ }
+
+ void write(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_METADATA);
+ serializer.attributeLong(null, XML_ATTR_ID, mId);
+ serializer.attributeInt(null, XML_ATTR_VERSION, VERSION);
+ for (TimeFrame timeFrame : mTimeFrames) {
+ timeFrame.write(serializer);
+ }
+ for (String section : mSections) {
+ serializer.startTag(null, XML_TAG_SECTION);
+ serializer.attribute(null, XML_ATTR_SECTION_TYPE, section);
+ serializer.endTag(null, XML_TAG_SECTION);
+ }
+ serializer.endTag(null, XML_TAG_METADATA);
+ }
+
+ /**
+ * Reads just the header of the XML file containing metadata.
+ * Returns null if the file does not contain a compatible &lt;metadata&gt; element.
+ */
+ @Nullable
+ public static Metadata read(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ Metadata metadata = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_METADATA))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if (tagName.equals(XML_TAG_METADATA)) {
+ int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+ if (!isCompatibleXmlFormat(version)) {
+ Slog.e(TAG,
+ "Incompatible version " + version + "; expected " + VERSION);
+ return null;
+ }
+
+ long id = parser.getAttributeLong(null, XML_ATTR_ID);
+ metadata = new Metadata(id);
+ } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) {
+ metadata.addTimeFrame(TimeFrame.read(parser));
+ } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) {
+ metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE));
+ }
+ }
+ eventType = parser.next();
+ }
+ return metadata;
+ }
+
+ /**
+ * Prints the metadata.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ dump(pw, true);
+ }
+
+ void dump(IndentingPrintWriter pw, boolean includeSections) {
+ pw.print("Span ");
+ if (mTimeFrames.size() > 0) {
+ mTimeFrames.get(0).dump(pw);
+ pw.println();
+ }
+
+ // Sometimes, when the wall clock is adjusted in the middle of a stats session,
+ // we will have more than one time frame.
+ for (int i = 1; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ pw.print(" "); // Aligned below "Span "
+ timeFrame.dump(pw);
+ pw.println();
+ }
+
+ if (includeSections) {
+ pw.increaseIndent();
+ for (String section : mSections) {
+ pw.print("section", section);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+ ipw.print("id", mId);
+ for (int i = 0; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ ipw.print("timeframe=[");
+ timeFrame.dump(ipw);
+ ipw.print("] ");
+ }
+ for (String section : mSections) {
+ ipw.print("section", section);
+ }
+ ipw.flush();
+ return sw.toString().trim();
+ }
+ }
+
+ /**
+ * Contains a specific type of aggregate power stats. The contents type is determined by
+ * the section type.
+ */
+ public abstract static class Section {
+ private final String mType;
+
+ Section(String type) {
+ mType = type;
+ }
+
+ /**
+ * Returns the section type, which determines the type of data stored in the corresponding
+ * section of {@link PowerStatsSpan}
+ */
+ public String getType() {
+ return mType;
+ }
+
+ abstract void write(TypedXmlSerializer serializer) throws IOException;
+
+ /**
+ * Prints the section type.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println(mType);
+ }
+ }
+
+ /**
+ * A universal XML parser for {@link PowerStatsSpan.Section}'s. It is aware of all
+ * supported section types as well as their corresponding XML formats.
+ */
+ public interface SectionReader {
+ /**
+ * Reads the contents of the section using the parser. The type of the object
+ * read and the corresponding XML format are determined by the section type.
+ */
+ Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+ }
+
+ private final Metadata mMetadata;
+ private final List<Section> mSections = new ArrayList<>();
+
+ public PowerStatsSpan(long id) {
+ this(new Metadata(id));
+ }
+
+ private PowerStatsSpan(Metadata metadata) {
+ mMetadata = metadata;
+ }
+
+ public Metadata getMetadata() {
+ return mMetadata;
+ }
+
+ public long getId() {
+ return mMetadata.mId;
+ }
+
+ void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
+ @DurationMillisLong long duration) {
+ mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration));
+ }
+
+ void addSection(Section section) {
+ mMetadata.addSection(section.getType());
+ mSections.add(section);
+ }
+
+ @NonNull
+ public List<Section> getSections() {
+ return mSections;
+ }
+
+ private static boolean isCompatibleXmlFormat(int version) {
+ return version == VERSION;
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the power stats span.
+ */
+ @VisibleForTesting
+ public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ mMetadata.write(serializer);
+ for (Section section : mSections) {
+ serializer.startTag(null, XML_TAG_SECTION);
+ serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType);
+ section.write(serializer);
+ serializer.endTag(null, XML_TAG_SECTION);
+ }
+ serializer.endDocument();
+ }
+
+ @Nullable
+ static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser,
+ SectionReader sectionReader, String... sectionTypes)
+ throws IOException, XmlPullParserException {
+ Set<String> neededSections = Sets.newArraySet(sectionTypes);
+ boolean selectSections = !neededSections.isEmpty();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+
+ Metadata metadata = Metadata.read(parser);
+ if (metadata == null) {
+ return null;
+ }
+
+ PowerStatsSpan span = new PowerStatsSpan(metadata);
+ boolean skipSection = false;
+ int nestingLevel = 0;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (skipSection) {
+ if (eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_SECTION)) {
+ nestingLevel--;
+ if (nestingLevel == 0) {
+ skipSection = false;
+ }
+ } else if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_SECTION)) {
+ nestingLevel++;
+ }
+ } else if (eventType == XmlPullParser.START_TAG) {
+ String tag = parser.getName();
+ if (tag.equals(XML_TAG_SECTION)) {
+ String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE);
+ if (!selectSections || neededSections.contains(sectionType)) {
+ Section section = sectionReader.read(sectionType, parser);
+ if (section == null) {
+ if (selectSections) {
+ throw new XmlPullParserException(
+ "Unsupported PowerStatsStore section type: " + sectionType);
+ } else {
+ section = new Section(sectionType) {
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("Unsupported PowerStatsStore section type: "
+ + sectionType);
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) {
+ }
+ };
+ }
+ }
+ span.addSection(section);
+ } else {
+ skipSection = true;
+ }
+ } else if (tag.equals(XML_TAG_METADATA)) {
+ Metadata.read(parser);
+ }
+ }
+ eventType = parser.next();
+ }
+ return span;
+ }
+
+ /**
+ * Prints the contents of this power stats span.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ mMetadata.dump(ipw, /* includeSections */ false);
+ for (Section section : mSections) {
+ ipw.increaseIndent();
+ ipw.println(section.mType);
+ section.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
new file mode 100644
index 000000000000..7123bcb2d095
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryUsageStats;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A storage mechanism for aggregated power/battery stats.
+ */
+public class PowerStatsStore {
+ private static final String TAG = "PowerStatsStore";
+
+ private static final String POWER_STATS_DIR = "power-stats";
+ private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss";
+ private static final String DIR_LOCK_FILENAME = ".lock";
+ private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024;
+
+ private final File mSystemDir;
+ private final File mStoreDir;
+ private final File mLockFile;
+ private final ReentrantLock mFileLock = new ReentrantLock();
+ private FileLock mJvmLock;
+ private final long mMaxStorageBytes;
+ private final Handler mHandler;
+ private final PowerStatsSpan.SectionReader mSectionReader;
+ private volatile List<PowerStatsSpan.Metadata> mTableOfContents;
+
+ public PowerStatsStore(@NonNull File systemDir, Handler handler,
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler,
+ new DefaultSectionReader(aggregatedPowerStatsConfig));
+ }
+
+ @VisibleForTesting
+ public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler,
+ @NonNull PowerStatsSpan.SectionReader sectionReader) {
+ mSystemDir = systemDir;
+ mStoreDir = new File(systemDir, POWER_STATS_DIR);
+ mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
+ mHandler = handler;
+ mMaxStorageBytes = maxStorageBytes;
+ mSectionReader = sectionReader;
+ mHandler.post(this::maybeClearLegacyStore);
+ }
+
+ /**
+ * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store.
+ */
+ @NonNull
+ public List<PowerStatsSpan.Metadata> getTableOfContents() {
+ List<PowerStatsSpan.Metadata> toc = mTableOfContents;
+ if (toc != null) {
+ return toc;
+ }
+
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ lockStoreDirectory();
+ try {
+ toc = new ArrayList<>();
+ for (File file : mStoreDir.listFiles()) {
+ String fileName = file.getName();
+ if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ continue;
+ }
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser);
+ if (metadata != null) {
+ toc.add(metadata);
+ } else {
+ Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName);
+ file.delete();
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName);
+ }
+ }
+ toc.sort(PowerStatsSpan.Metadata.COMPARATOR);
+ mTableOfContents = Collections.unmodifiableList(toc);
+ } finally {
+ unlockStoreDirectory();
+ }
+
+ return toc;
+ }
+
+ /**
+ * Saves the specified span in the store.
+ */
+ public void storePowerStatsSpan(PowerStatsSpan span) {
+ maybeClearLegacyStore();
+ lockStoreDirectory();
+ try {
+ if (!mStoreDir.exists()) {
+ if (!mStoreDir.mkdirs()) {
+ Slog.e(TAG, "Could not create a directory for power stats store");
+ return;
+ }
+ }
+
+ AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId()));
+ file.write(out-> {
+ try {
+ span.writeXml(out, Xml.newBinarySerializer());
+ } catch (Exception e) {
+ // AtomicFile will log the exception and delete the file.
+ throw new RuntimeException(e);
+ }
+ });
+ mTableOfContents = null;
+ removeOldSpansLocked();
+ } finally {
+ unlockStoreDirectory();
+ }
+ }
+
+ /**
+ * Loads the PowerStatsSpan identified by its ID. Only loads the sections with
+ * the specified types. Loads all sections if no sectionTypes is empty.
+ */
+ @Nullable
+ public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) {
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ lockStoreDirectory();
+ try {
+ File file = makePowerStatsSpanFilename(id);
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file);
+ }
+ } finally {
+ unlockStoreDirectory();
+ }
+ return null;
+ }
+
+ void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+ PowerStatsSpan span = createPowerStatsSpan(stats);
+ if (span == null) {
+ return;
+ }
+ storePowerStatsSpan(span);
+ }
+
+ static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
+ List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+ if (clockUpdates.isEmpty()) {
+ Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
+ return null;
+ }
+
+ long monotonicTime = clockUpdates.get(0).monotonicTime;
+ long durationSum = 0;
+ PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
+ for (int i = 0; i < clockUpdates.size(); i++) {
+ AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
+ long duration;
+ if (i == clockUpdates.size() - 1) {
+ duration = stats.getDuration() - durationSum;
+ } else {
+ duration = clockUpdate.monotonicTime - monotonicTime;
+ }
+ span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
+ monotonicTime = clockUpdate.monotonicTime;
+ durationSum += duration;
+ }
+
+ span.addSection(new AggregatedPowerStatsSection(stats));
+ return span;
+ }
+
+ /**
+ * Stores a {@link PowerStatsSpan} containing a single section for the supplied
+ * battery usage stats.
+ */
+ public void storeBatteryUsageStats(long monotonicStartTime,
+ BatteryUsageStats batteryUsageStats) {
+ PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+ span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+ batteryUsageStats.getStatsDuration());
+ span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+ storePowerStatsSpan(span);
+ }
+
+ /**
+ * Creates a file name by formatting the span ID as a 19-digit zero-padded number.
+ * This ensures that the lexicographically sorted directory follows the chronological order.
+ */
+ private File makePowerStatsSpanFilename(long id) {
+ return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id)
+ + POWER_STATS_SPAN_FILE_EXTENSION);
+ }
+
+ private void maybeClearLegacyStore() {
+ File legacyStoreDir = new File(mSystemDir, "battery-usage-stats");
+ if (legacyStoreDir.exists()) {
+ FileUtils.deleteContentsAndDir(legacyStoreDir);
+ }
+ }
+
+ private void lockStoreDirectory() {
+ mFileLock.lock();
+
+ // Lock the directory from access by other JVMs
+ try {
+ mLockFile.getParentFile().mkdirs();
+ mLockFile.createNewFile();
+ mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot lock snapshot directory", e);
+ }
+ }
+
+ private void unlockStoreDirectory() {
+ try {
+ mJvmLock.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot unlock snapshot directory", e);
+ } finally {
+ mFileLock.unlock();
+ }
+ }
+
+ private void removeOldSpansLocked() {
+ // Read the directory list into a _sorted_ map. The alphanumeric ordering
+ // corresponds to the historical order of snapshots because the file names
+ // are timestamps zero-padded to the same length.
+ long totalSize = 0;
+ TreeMap<File, Long> mFileSizes = new TreeMap<>();
+ for (File file : mStoreDir.listFiles()) {
+ final long fileSize = file.length();
+ totalSize += fileSize;
+ if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ mFileSizes.put(file, fileSize);
+ }
+ }
+
+ while (totalSize > mMaxStorageBytes) {
+ final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
+ if (entry == null) {
+ break;
+ }
+
+ File file = entry.getKey();
+ if (!file.delete()) {
+ Slog.e(TAG, "Cannot delete power stats span " + file);
+ }
+ totalSize -= entry.getValue();
+ mFileSizes.remove(file);
+ mTableOfContents = null;
+ }
+ }
+
+ /**
+ * Deletes all contents from the store.
+ */
+ public void reset() {
+ lockStoreDirectory();
+ try {
+ for (File file : mStoreDir.listFiles()) {
+ if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ if (!file.delete()) {
+ Slog.e(TAG, "Cannot delete power stats span " + file);
+ }
+ }
+ }
+ mTableOfContents = List.of();
+ } finally {
+ unlockStoreDirectory();
+ }
+ }
+
+ /**
+ * Prints the summary of contents of the store: only metadata, but not the actual stored
+ * objects.
+ */
+ public void dumpTableOfContents(IndentingPrintWriter ipw) {
+ ipw.println("Power stats store TOC");
+ ipw.increaseIndent();
+ List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+ for (PowerStatsSpan.Metadata metadata : contents) {
+ metadata.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Prints the contents of the store.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("Power stats store");
+ ipw.increaseIndent();
+ List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+ for (PowerStatsSpan.Metadata metadata : contents) {
+ PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
+ if (span != null) {
+ span.dump(ipw);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
+ private static class DefaultSectionReader implements PowerStatsSpan.SectionReader {
+ private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+ DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
+ }
+
+ @Override
+ public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ switch (sectionType) {
+ case AggregatedPowerStatsSection.TYPE:
+ return new AggregatedPowerStatsSection(
+ AggregatedPowerStats.createFromXml(parser,
+ mAggregatedPowerStatsConfig));
+ case BatteryUsageStatsSection.TYPE:
+ return new BatteryUsageStatsSection(
+ BatteryUsageStats.createFromXml(parser));
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index d61bebc0c82c..add806febe67 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -1,6 +1,13 @@
package: "com.android.server.power.optimization"
flag {
+ name: "power_monitor_api"
+ namespace: "power_optimization"
+ description: "Feature flag for ODPM API"
+ bug: "295027807"
+}
+
+flag {
name: "streamlined_battery_stats"
namespace: "power_optimization"
description: "Feature flag for streamlined battery stats"
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 5609f6975bc4..77290fd944eb 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -694,7 +694,7 @@ public class PowerStatsService extends SystemService {
Log.d(TAG, String.format(Locale.ENGLISH,
"Monitor=%s timestamp=%d energy=%d"
+ " uid=%d noise=%.1f%% returned=%d",
- state.powerMonitor.name,
+ state.powerMonitor.getName(),
state.timestampMs,
state.energyUws,
callingUid,
@@ -728,7 +728,7 @@ public class PowerStatsService extends SystemService {
}
for (PowerMonitorState powerMonitorState : powerMonitorStates) {
- if (powerMonitorState.powerMonitor.type
+ if (powerMonitorState.powerMonitor.getType()
== PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {
for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {
if (energyConsumerResult.id == powerMonitorState.id) {
@@ -754,7 +754,7 @@ public class PowerStatsService extends SystemService {
}
for (PowerMonitorState powerMonitorState : powerMonitorStates) {
- if (powerMonitorState.powerMonitor.type
+ if (powerMonitorState.powerMonitor.getType()
== PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {
for (EnergyMeasurement energyMeasurement : energyMeasurements) {
if (energyMeasurement.id == powerMonitorState.id) {
@@ -773,7 +773,7 @@ public class PowerStatsService extends SystemService {
@PowerMonitor.PowerMonitorType int type) {
int count = 0;
for (PowerMonitorState monitorState : powerMonitorStates) {
- if (monitorState.powerMonitor.type == type) {
+ if (monitorState.powerMonitor.getType() == type) {
count++;
}
}
@@ -785,7 +785,7 @@ public class PowerStatsService extends SystemService {
int[] ids = new int[count];
int index = 0;
for (PowerMonitorState monitorState : powerMonitorStates) {
- if (monitorState.powerMonitor.type == type) {
+ if (monitorState.powerMonitor.getType() == type) {
ids[index++] = monitorState.id;
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index c9db343697df..0656a6a7d3e9 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -174,8 +174,6 @@ public final class SensorPrivacyService extends SystemService {
private CallStateHelper mCallStateHelper;
private KeyguardManager mKeyguardManager;
- private SafetyCenterManager mSafetyCenterManager;
-
private int mCurrentUser = USER_NULL;
public SensorPrivacyService(Context context) {
@@ -191,7 +189,6 @@ public final class SensorPrivacyService extends SystemService {
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
- mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);
}
@Override
@@ -656,7 +653,9 @@ public final class SensorPrivacyService extends SystemService {
String contentTitle = getUiContext().getString(messageRes);
Spanned contentText = Html.fromHtml(getUiContext().getString(
R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0);
- String action = mSafetyCenterManager.isSafetyCenterEnabled()
+ SafetyCenterManager safetyCenterManager =
+ mContext.getSystemService(SafetyCenterManager.class);
+ String action = safetyCenterManager.isSafetyCenterEnabled()
? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS;
PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor,
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index bfe34049e1e5..9a9b83602832 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -41,8 +41,8 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemService;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.UserManagerInternal;
import java.io.ByteArrayInputStream;
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index a5c0fb3c46af..cddc79db6106 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1047,6 +1047,16 @@ public class TunerResourceManagerService extends SystemService implements IBinde
// in use frontends when no available frontend has been found.
int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
if (currentLowestPriority > priority) {
+ // we need to check the max used num if the target frontend type is not
+ // currently in primary use (and simply blocked due to exclusive group)
+ ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
+ int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+ FrontendResource primaryFe = getFrontendResource(primaryFeId);
+ if (fr.getType() != primaryFe.getType()
+ && isFrontendMaxNumUseReached(fr.getType())) {
+ continue;
+ }
+ // update the target frontend
inUseLowestPriorityFrHandle = fr.getHandle();
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index e4f960763d54..a34621642bcd 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,6 +19,7 @@ package com.android.server.vibrator;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.VibratorInfo;
import android.os.vibrator.persistence.ParsedVibration;
import android.os.vibrator.persistence.VibrationXmlParser;
@@ -127,6 +128,10 @@ final class HapticFeedbackCustomization {
VibrationXmlParser.VibrationXmlParserException,
XmlParserException,
XmlPullParserException {
+ if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) {
+ Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
+ return null;
+ }
String customizationFile =
res.getString(
com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 01ea33f1aecd..0718f2f284a5 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2843,7 +2843,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
boolean systemValid = systemWallpaper != null;
- boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled();
+ boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();
return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
: systemValid ? new WallpaperData[]{systemWallpaper}
: lockValid ? new WallpaperData[]{lockWallpaper}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c866dd013af0..a01113b26a1e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1316,6 +1316,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mLaunchIntoPipHostActivity != null) {
pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);
}
+ if (mWaitForEnteringPinnedMode) {
+ pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true");
+ }
mLetterboxUiController.dump(pw, prefix);
@@ -3132,9 +3135,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
boolean canReceiveKeys() {
- // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent
- return getWindowConfiguration().canReceiveKeys()
- && (task == null || task.getWindowConfiguration().canReceiveKeys());
+ return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
}
boolean isResizeable() {
@@ -8264,7 +8265,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private void clearSizeCompatModeAttributes() {
mInSizeCompatModeForBounds = false;
+ final float lastSizeCompatScale = mSizeCompatScale;
mSizeCompatScale = 1f;
+ if (mSizeCompatScale != lastSizeCompatScale) {
+ forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
+ }
mSizeCompatBounds = null;
mCompatDisplayInsets = null;
mLetterboxUiController.clearInheritedCompatDisplayInsets();
@@ -8272,11 +8277,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@VisibleForTesting
void clearSizeCompatMode() {
- final float lastSizeCompatScale = mSizeCompatScale;
clearSizeCompatModeAttributes();
- if (mSizeCompatScale != lastSizeCompatScale) {
- forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
- }
// Clear config override in #updateCompatDisplayInsets().
final int activityType = getActivityType();
final Configuration overrideConfig = getRequestedOverrideConfiguration();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0b673211a1c9..de335d3d013e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,6 +68,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3686,6 +3687,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
getTransitionController(), mWindowManager.mSyncEngine)
: null;
+ if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
+ && transition != null) {
+ transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
final Runnable enterPipRunnable = () -> {
synchronized (mGlobalLock) {
if (r.getParent() == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12e1e2cd1e41..777b5cd4337b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2832,17 +2832,22 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
private ActivityRecord mStarting;
private boolean mIncludeInvisibleAndFinishing;
+ private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
+ ActivityRecord getOpaqueActivity(
+ @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
mIncludeInvisibleAndFinishing = true;
+ mIgnoringKeyguard = ignoringKeyguard;
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
}
- ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container,
- @Nullable ActivityRecord starting) {
+ ActivityRecord getVisibleOpaqueActivity(
+ @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting,
+ boolean ignoringKeyguard) {
mStarting = starting;
mIncludeInvisibleAndFinishing = false;
+ mIgnoringKeyguard = ignoringKeyguard;
final ActivityRecord opaque = container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
mStarting = null;
@@ -2851,7 +2856,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
@Override
public boolean test(ActivityRecord r) {
- if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) {
+ if (!mIncludeInvisibleAndFinishing && r != mStarting
+ && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard)
+ || (!mIgnoringKeyguard && !r.isVisible()))) {
// Ignore invisible activities that are not the currently starting activity
// (about to be visible).
return false;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index b039646c1697..3dc377dbc14c 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -22,6 +22,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -31,7 +32,8 @@ import java.io.PrintWriter;
* Interface that describes an animation and bridges the animation start to the component
* responsible for running the animation.
*/
-interface AnimationAdapter {
+@VisibleForTesting
+public interface AnimationAdapter {
long STATUS_BAR_TRANSITION_DURATION = 120L;
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index ae29afa9fc49..64a230effb38 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,172 +11,36 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.wm;
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-
import android.annotation.NonNull;
import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-import java.io.PrintWriter;
+import com.android.window.flags.Flags;
/**
* Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
* black layers of varying opacity at various Z-levels which create the effect of a Dim.
*/
-class Dimmer {
- private static final String TAG = "WindowManager";
- // This is in milliseconds.
- private static final int DEFAULT_DIM_ANIM_DURATION = 200;
-
- private class DimAnimatable implements SurfaceAnimator.Animatable {
- private SurfaceControl mDimLayer;
-
- private DimAnimatable(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mHost.getSyncTransaction();
- }
-
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mHost.getPendingTransaction();
- }
-
- @Override
- public void commitPendingTransaction() {
- mHost.commitPendingTransaction();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- }
-
- @Override
- public SurfaceControl.Builder makeAnimationLeash() {
- return mHost.makeAnimationLeash();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public SurfaceControl getSurfaceControl() {
- return mDimLayer;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public int getSurfaceWidth() {
- // This will determine the size of the leash created. This should be the size of the
- // host and not the dim layer since the dim layer may get bigger during animation. If
- // that occurs, the leash size cannot change so we need to ensure the leash is big
- // enough that the dim layer can grow.
- // This works because the mHost will be a Task which has the display bounds.
- return mHost.getSurfaceWidth();
- }
-
- @Override
- public int getSurfaceHeight() {
- // See getSurfaceWidth() above for explanation.
- return mHost.getSurfaceHeight();
- }
-
- void removeSurface() {
- if (mDimLayer != null && mDimLayer.isValid()) {
- getSyncTransaction().remove(mDimLayer);
- }
- mDimLayer = null;
- }
- }
-
- @VisibleForTesting
- class DimState {
- /**
- * The layer where property changes should be invoked on.
- */
- SurfaceControl mDimLayer;
- boolean mDimming;
- boolean isVisible;
- SurfaceAnimator mSurfaceAnimator;
-
- // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
- final Rect mDimBounds = new Rect();
-
- /**
- * Determines whether the dim layer should animate before destroying.
- */
- boolean mAnimateExit = true;
-
- /**
- * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
- * details on Dim lifecycle.
- */
- boolean mDontReset;
-
- DimState(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- mDimming = true;
- final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
- mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
- if (!mDimming) {
- dimAnimatable.removeSurface();
- }
- }, mHost.mWmService);
- }
- }
-
+public abstract class Dimmer {
/**
- * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+ * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
* host, some controller of it, or one of the hosts children.
*/
- private WindowContainer mHost;
- private WindowContainer mLastRequestedDimContainer;
- @VisibleForTesting
- DimState mDimState;
-
- private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
- @VisibleForTesting
- interface SurfaceAnimatorStarter {
- void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @AnimationType int type);
- }
+ protected final WindowContainer mHost;
- Dimmer(WindowContainer host) {
- this(host, SurfaceAnimator::startAnimation);
+ protected Dimmer(WindowContainer host) {
+ mHost = host;
}
- Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
- mHost = host;
- mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+ // Constructs the correct type of dimmer
+ static Dimmer create(WindowContainer host) {
+ return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host);
}
@NonNull
@@ -184,49 +48,8 @@ class Dimmer {
return mHost;
}
- private SurfaceControl makeDimLayer() {
- return mHost.makeChildSurface(null)
- .setParent(mHost.getSurfaceControl())
- .setColorLayer()
- .setName("Dim Layer for - " + mHost.getName())
- .setCallsite("Dimmer.makeDimLayer")
- .build();
- }
-
- /**
- * Retrieve the DimState, creating one if it doesn't exist.
- */
- private DimState getDimState(WindowContainer container) {
- if (mDimState == null) {
- try {
- final SurfaceControl ctl = makeDimLayer();
- mDimState = new DimState(ctl);
- } catch (Surface.OutOfResourcesException e) {
- Log.w(TAG, "OutOfResourcesException creating dim surface");
- }
- }
-
- mLastRequestedDimContainer = container;
- return mDimState;
- }
-
- private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
- final DimState d = getDimState(container);
-
- if (d == null) {
- return;
- }
-
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- SurfaceControl.Transaction t = mHost.getPendingTransaction();
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- t.setAlpha(d.mDimLayer, alpha);
- t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-
- d.mDimming = true;
- }
+ protected abstract void dim(
+ WindowContainer container, int relativeLayer, float alpha, int blurRadius);
/**
* Place a dim above the given container, which should be a child of the host container.
@@ -260,25 +83,15 @@ class Dimmer {
* chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
* a chance to request dims to continue.
*/
- void resetDimStates() {
- if (mDimState == null) {
- return;
- }
- if (!mDimState.mDontReset) {
- mDimState.mDimming = false;
- }
- }
+ abstract void resetDimStates();
/** Returns non-null bounds if the dimmer is showing. */
- Rect getDimBounds() {
- return mDimState != null ? mDimState.mDimBounds : null;
- }
+ abstract Rect getDimBounds();
- void dontAnimateExit() {
- if (mDimState != null) {
- mDimState.mAnimateExit = false;
- }
- }
+ abstract void dontAnimateExit();
+
+ @VisibleForTesting
+ abstract SurfaceControl getDimLayer();
/**
* Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -288,109 +101,5 @@ class Dimmer {
* @param t A transaction in which to update the dims.
* @return true if any Dims were updated.
*/
- boolean updateDims(SurfaceControl.Transaction t) {
- if (mDimState == null) {
- return false;
- }
-
- if (!mDimState.mDimming) {
- if (!mDimState.mAnimateExit) {
- if (mDimState.mDimLayer.isValid()) {
- t.remove(mDimState.mDimLayer);
- }
- } else {
- startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
- }
- mDimState = null;
- return false;
- } else {
- final Rect bounds = mDimState.mDimBounds;
- // TODO: Once we use geometry from hierarchy this falls away.
- t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
- t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
- if (!mDimState.isVisible) {
- mDimState.isVisible = true;
- t.show(mDimState.mDimLayer);
- // Skip enter animation while starting window is on top of its activity
- final WindowState ws = mLastRequestedDimContainer.asWindowState();
- if (ws == null || ws.mActivityRecord == null
- || ws.mActivityRecord.mStartingData == null) {
- startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
- }
- }
- return true;
- }
- }
-
- private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
- }
-
- private void startDimExit(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
- }
-
- private void startAnim(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
- mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
- new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
- mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_DIMMER);
- }
-
- private long getDimDuration(WindowContainer container) {
- // If there's no container, then there isn't an animation occurring while dimming. Set the
- // duration to 0 so it immediately dims to the set alpha.
- if (container == null) {
- return 0;
- }
-
- // Otherwise use the same duration as the animation on the WindowContainer
- AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
- final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
- return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
- : animationAdapter.getDurationHint();
- }
-
- private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
- private final long mDuration;
- private final float mFromAlpha;
- private final float mToAlpha;
-
- AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
- mFromAlpha = fromAlpha;
- mToAlpha = toAlpha;
- mDuration = duration;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
- t.setAlpha(sc, alpha);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
- pw.print(" to="); pw.print(mToAlpha);
- pw.print(" duration="); pw.println(mDuration);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ALPHA);
- proto.write(FROM, mFromAlpha);
- proto.write(TO, mToAlpha);
- proto.write(DURATION_MS, mDuration);
- proto.end(token);
- }
- }
+ abstract boolean updateDims(SurfaceControl.Transaction t);
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index df26b101a657..f51bf7fbf27a 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@ import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
-
/**
* Container for grouping WindowContainer below DisplayContent.
*
@@ -786,7 +785,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
* DisplayArea that can be dimmed.
*/
static class Dimmable extends DisplayArea<DisplayArea> {
- private final Dimmer mDimmer = new Dimmer(this);
+ private final Dimmer mDimmer = Dimmer.create(this);
Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930dab5eb..1fa7d2a2aa13 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -51,7 +51,8 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
private final Rect mOldWindowCrop = new Rect();
InputConsumerImpl(WindowManagerService service, IBinder token, String name,
- InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) {
+ InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId,
+ SurfaceControl.Transaction t) {
mService = service;
mToken = token;
mName = name;
@@ -82,6 +83,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
.setName("Input Consumer " + name)
.setCallsite("InputConsumerImpl")
.build();
+ mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
}
void linkToDeathRecipient() {
@@ -129,14 +131,12 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
void show(SurfaceControl.Transaction t, WindowContainer w) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
}
void show(SurfaceControl.Transaction t, int layer) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, layer);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index af307ec3c2a9..5c0bc28779a8 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -224,7 +224,7 @@ final class InputMonitor {
}
final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
- inputChannel, clientPid, clientUser, mDisplayId);
+ inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction);
switch (name) {
case INPUT_CONSUMER_WALLPAPER:
consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER;
@@ -675,11 +675,6 @@ final class InputMonitor {
w.getKeyInterceptionInfo());
if (w.mWinAnimator.hasSurface()) {
- // Update trusted overlay changes here because they are tied to input info. Input
- // changes can be updated even if surfaces aren't.
- inputWindowHandle.setTrustedOverlay(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl,
- w.isWindowTrustedOverlay());
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
new file mode 100644
index 000000000000..ccf956ecef1e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyDimmer.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+public class LegacyDimmer extends Dimmer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ // This is in milliseconds.
+ private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+ DimState mDimState;
+ private WindowContainer mLastRequestedDimContainer;
+ private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+ private class DimAnimatable implements SurfaceAnimator.Animatable {
+ private SurfaceControl mDimLayer;
+
+ private DimAnimatable(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ }
+
+ @Override
+ public SurfaceControl.Transaction getSyncTransaction() {
+ return mHost.getSyncTransaction();
+ }
+
+ @Override
+ public SurfaceControl.Transaction getPendingTransaction() {
+ return mHost.getPendingTransaction();
+ }
+
+ @Override
+ public void commitPendingTransaction() {
+ mHost.commitPendingTransaction();
+ }
+
+ @Override
+ public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+ }
+
+ @Override
+ public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public SurfaceControl.Builder makeAnimationLeash() {
+ return mHost.makeAnimationLeash();
+ }
+
+ @Override
+ public SurfaceControl getAnimationLeashParent() {
+ return mHost.getSurfaceControl();
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl() {
+ return mDimLayer;
+ }
+
+ @Override
+ public SurfaceControl getParentSurfaceControl() {
+ return mHost.getSurfaceControl();
+ }
+
+ @Override
+ public int getSurfaceWidth() {
+ // This will determine the size of the leash created. This should be the size of the
+ // host and not the dim layer since the dim layer may get bigger during animation. If
+ // that occurs, the leash size cannot change so we need to ensure the leash is big
+ // enough that the dim layer can grow.
+ // This works because the mHost will be a Task which has the display bounds.
+ return mHost.getSurfaceWidth();
+ }
+
+ @Override
+ public int getSurfaceHeight() {
+ // See getSurfaceWidth() above for explanation.
+ return mHost.getSurfaceHeight();
+ }
+
+ void removeSurface() {
+ if (mDimLayer != null && mDimLayer.isValid()) {
+ getSyncTransaction().remove(mDimLayer);
+ }
+ mDimLayer = null;
+ }
+ }
+
+ @VisibleForTesting
+ class DimState {
+ /**
+ * The layer where property changes should be invoked on.
+ */
+ SurfaceControl mDimLayer;
+ boolean mDimming;
+ boolean mIsVisible;
+
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ /**
+ * Determines whether the dim layer should animate before destroying.
+ */
+ boolean mAnimateExit = true;
+
+ /**
+ * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
+ * details on Dim lifecycle.
+ */
+ boolean mDontReset;
+ SurfaceAnimator mSurfaceAnimator;
+
+ DimState(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ mDimming = true;
+ final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
+ mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
+ if (!mDimming) {
+ dimAnimatable.removeSurface();
+ }
+ }, mHost.mWmService);
+ }
+ }
+
+ @VisibleForTesting
+ interface SurfaceAnimatorStarter {
+ void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+ AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
+ }
+
+ protected LegacyDimmer(WindowContainer host) {
+ this(host, SurfaceAnimator::startAnimation);
+ }
+
+ LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+ super(host);
+ mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+ }
+
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ try {
+ final SurfaceControl ctl = makeDimLayer();
+ mDimState = new DimState(ctl);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("Dimmer.makeDimLayer")
+ .build();
+ }
+
+ @Override
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimLayer : null;
+ }
+
+ @Override
+ void resetDimStates() {
+ if (mDimState == null) {
+ return;
+ }
+ if (!mDimState.mDontReset) {
+ mDimState.mDimming = false;
+ }
+ }
+
+ @Override
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ @Override
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
+
+ @Override
+ protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+
+ if (d == null) {
+ return;
+ }
+
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ SurfaceControl.Transaction t = mHost.getPendingTransaction();
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
+ t.setAlpha(d.mDimLayer, alpha);
+ t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
+
+ d.mDimming = true;
+ }
+
+ @Override
+ boolean updateDims(SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+
+ if (!mDimState.mDimming) {
+ if (!mDimState.mAnimateExit) {
+ if (mDimState.mDimLayer.isValid()) {
+ t.remove(mDimState.mDimLayer);
+ }
+ } else {
+ startDimExit(mLastRequestedDimContainer,
+ mDimState.mSurfaceAnimator, t);
+ }
+ mDimState = null;
+ return false;
+ } else {
+ final Rect bounds = mDimState.mDimBounds;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+ if (!mDimState.mIsVisible) {
+ mDimState.mIsVisible = true;
+ t.show(mDimState.mDimLayer);
+ // Skip enter animation while starting window is on top of its activity
+ final WindowState ws = mLastRequestedDimContainer.asWindowState();
+ if (ws == null || ws.mActivityRecord == null
+ || ws.mActivityRecord.mStartingData == null) {
+ startDimEnter(mLastRequestedDimContainer,
+ mDimState.mSurfaceAnimator, t);
+ }
+ }
+ return true;
+ }
+ }
+
+ private long getDimDuration(WindowContainer container) {
+ // Use the same duration as the animation on the WindowContainer
+ AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+ final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+ return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+ : animationAdapter.getDurationHint();
+ }
+
+ private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t) {
+ startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
+ }
+
+ private void startDimExit(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t) {
+ startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
+ }
+
+ private void startAnim(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
+ mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
+ new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
+ mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+ ANIMATION_TYPE_DIMMER);
+ }
+
+ private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+ private final long mDuration;
+ private final float mFromAlpha;
+ private final float mToAlpha;
+
+ AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
+ mFromAlpha = fromAlpha;
+ mToAlpha = toAlpha;
+ mDuration = duration;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ final float fraction = getFraction(currentPlayTime);
+ final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
+ t.setAlpha(sc, alpha);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
+ pw.print(" to="); pw.print(mToAlpha);
+ pw.print(" duration="); pw.println(mDuration);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(ALPHA);
+ proto.write(FROM, mFromAlpha);
+ proto.write(TO, mToAlpha);
+ proto.write(DURATION_MS, mDuration);
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 458786ffdbc0..f6c364008c62 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,4 +18,4 @@ rgl@google.com
yunfanc@google.com
per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com \ No newline at end of file
+per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf6a1feef5ee..7a442e708130 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2211,6 +2211,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
organizedTf);
}
+
+ if (taskDisplayArea.getFocusedRootTask() == rootTask) {
+ taskDisplayArea.clearPreferredTopFocusableRootTask();
+ }
} finally {
mService.continueWindowLayout();
try {
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
new file mode 100644
index 000000000000..6ddbd2c8eb67
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+class SmoothDimmer extends Dimmer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ private static final float EPSILON = 0.0001f;
+ // This is in milliseconds.
+ private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+ DimState mDimState;
+ private WindowContainer mLastRequestedDimContainer;
+ private final AnimationAdapterFactory mAnimationAdapterFactory;
+
+ @VisibleForTesting
+ class DimState {
+ /**
+ * The layer where property changes should be invoked on.
+ */
+ SurfaceControl mDimLayer;
+ boolean mDimming;
+ boolean mIsVisible;
+
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ /**
+ * Determines whether the dim layer should animate before destroying.
+ */
+ boolean mAnimateExit = true;
+
+ /**
+ * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
+ * details on Dim lifecycle.
+ */
+ boolean mDontReset;
+
+ Change mCurrentProperties;
+ Change mRequestedProperties;
+ private AnimationSpec mAlphaAnimationSpec;
+ private AnimationAdapter mLocalAnimationAdapter;
+
+ static class Change {
+ private float mAlpha = -1f;
+ private int mBlurRadius = -1;
+ private WindowContainer mDimmingContainer = null;
+ private int mRelativeLayer = -1;
+ private boolean mSkipAnimation = false;
+
+ Change() {}
+
+ Change(Change other) {
+ mAlpha = other.mAlpha;
+ mBlurRadius = other.mBlurRadius;
+ mDimmingContainer = other.mDimmingContainer;
+ mRelativeLayer = other.mRelativeLayer;
+ }
+
+ @Override
+ public String toString() {
+ return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+ + mDimmingContainer + ", relativePosition=" + mRelativeLayer
+ + ", skipAnimation=" + mSkipAnimation;
+ }
+ }
+
+ DimState(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ mDimming = true;
+ mCurrentProperties = new Change();
+ mRequestedProperties = new Change();
+ }
+
+ void setExitParameters(WindowContainer container) {
+ setRequestedParameters(container, -1, 0, 0);
+ }
+ // Sets a requested change without applying it immediately
+ void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha,
+ int blurRadius) {
+ mRequestedProperties.mDimmingContainer = container;
+ mRequestedProperties.mRelativeLayer = relativeLayer;
+ mRequestedProperties.mAlpha = alpha;
+ mRequestedProperties.mBlurRadius = blurRadius;
+ }
+
+ /**
+ * Commit the last changes we received. Called after
+ * {@link Change#setRequestedParameters(WindowContainer, int, float, int)}
+ */
+ void applyChanges(SurfaceControl.Transaction t) {
+ if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+ Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+ + "does not have a surface");
+ return;
+ }
+ if (!mDimState.mIsVisible) {
+ mDimState.mIsVisible = true;
+ t.show(mDimState.mDimLayer);
+ }
+ t.setRelativeLayer(mDimLayer,
+ mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+ mRequestedProperties.mRelativeLayer);
+
+ if (aspectChanged()) {
+ if (isAnimating()) {
+ mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
+ }
+ if (mRequestedProperties.mSkipAnimation
+ || (!dimmingContainerChanged() && mDimming)) {
+ // If the dimming container has not changed, then it is running its own
+ // animation, thus we can directly set the values we get requested, unless it's
+ // the exiting animation
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+ mDimLayer, mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius);
+ t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+ t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+ mRequestedProperties.mSkipAnimation = false;
+ } else {
+ startAnimation(t);
+ }
+ }
+ mCurrentProperties = new Change(mRequestedProperties);
+ }
+
+ private void startAnimation(SurfaceControl.Transaction t) {
+ mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius);
+ mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+ mHost.mWmService.mSurfaceAnimationRunner);
+
+ mLocalAnimationAdapter.startAnimation(mDimLayer, t,
+ ANIMATION_TYPE_DIMMER, (type, animator) -> {
+ t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+ t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+ if (mRequestedProperties.mAlpha == 0f && !mDimming) {
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Removing dim surface %s on transaction %s", mDimLayer, t);
+ t.remove(mDimLayer);
+ }
+ mLocalAnimationAdapter = null;
+ mAlphaAnimationSpec = null;
+ });
+ }
+
+ private boolean isAnimating() {
+ return mAlphaAnimationSpec != null;
+ }
+
+ private boolean aspectChanged() {
+ return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
+ || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
+ }
+
+ private boolean dimmingContainerChanged() {
+ return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
+ }
+
+ private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
+ final float startAlpha;
+ final int startBlur;
+ if (mAlphaAnimationSpec != null) {
+ startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
+ startBlur = mAlphaAnimationSpec.mCurrentBlur;
+ } else {
+ startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+ startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+ }
+ long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+ * Math.abs(targetAlpha - startAlpha));
+
+ ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
+ + "alpha: %f -> %f, blur: %d -> %d",
+ mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
+ startBlur, targetBlur);
+ return new AnimationSpec(
+ new AnimationExtremes<>(startAlpha, targetAlpha),
+ new AnimationExtremes<>(startBlur, targetBlur),
+ duration
+ );
+ }
+ }
+
+ protected SmoothDimmer(WindowContainer host) {
+ this(host, new AnimationAdapterFactory());
+ }
+
+ @VisibleForTesting
+ SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
+ super(host);
+ mAnimationAdapterFactory = animationFactory;
+ }
+
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ try {
+ final SurfaceControl ctl = makeDimLayer();
+ mDimState = new DimState(ctl);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("Dimmer.makeDimLayer")
+ .build();
+ }
+
+ @Override
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimLayer : null;
+ }
+
+ @Override
+ void resetDimStates() {
+ if (mDimState == null) {
+ return;
+ }
+ if (!mDimState.mDontReset) {
+ mDimState.mDimming = false;
+ }
+ }
+
+ @Override
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ @Override
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
+
+ @Override
+ protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+
+ mDimState.mRequestedProperties.mDimmingContainer = container;
+ mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius);
+ d.mDimming = true;
+ }
+
+ boolean updateDims(SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+
+ if (!mDimState.mDimming) {
+ // No one is dimming anymore, fade out dim and remove
+ if (!mDimState.mAnimateExit) {
+ if (mDimState.mDimLayer.isValid()) {
+ t.remove(mDimState.mDimLayer);
+ }
+ } else {
+ mDimState.setExitParameters(
+ mDimState.mRequestedProperties.mDimmingContainer);
+ mDimState.applyChanges(t);
+ }
+ mDimState = null;
+ return false;
+ }
+ final Rect bounds = mDimState.mDimBounds;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+ // Skip enter animation while starting window is on top of its activity
+ final WindowState ws = mLastRequestedDimContainer.asWindowState();
+ if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+ && ws.mActivityRecord.mStartingData != null) {
+ mDimState.mRequestedProperties.mSkipAnimation = true;
+ }
+ mDimState.applyChanges(t);
+ return true;
+ }
+
+ private long getDimDuration(WindowContainer container) {
+ // Use the same duration as the animation on the WindowContainer
+ AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+ final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+ return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+ : animationAdapter.getDurationHint();
+ }
+
+ private static class AnimationExtremes<T> {
+ final T mStartValue;
+ final T mFinishValue;
+
+ AnimationExtremes(T fromValue, T toValue) {
+ mStartValue = fromValue;
+ mFinishValue = toValue;
+ }
+ }
+
+ private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+ private final long mDuration;
+ private final AnimationExtremes<Float> mAlpha;
+ private final AnimationExtremes<Integer> mBlur;
+
+ float mCurrentAlpha = 0;
+ int mCurrentBlur = 0;
+
+ AnimationSpec(AnimationExtremes<Float> alpha,
+ AnimationExtremes<Integer> blur, long duration) {
+ mAlpha = alpha;
+ mBlur = blur;
+ mDuration = duration;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ final float fraction = getFraction(currentPlayTime);
+ mCurrentAlpha =
+ fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+ mCurrentBlur =
+ (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+ t.setAlpha(sc, mCurrentAlpha);
+ t.setBackgroundBlurRadius(sc, mCurrentBlur);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+ pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+ pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+ pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+ pw.print(" duration="); pw.println(mDuration);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(ALPHA);
+ proto.write(FROM, mAlpha.mStartValue);
+ proto.write(TO, mAlpha.mFinishValue);
+ proto.write(DURATION_MS, mDuration);
+ proto.end(token);
+ }
+ }
+
+ static class AnimationAdapterFactory {
+
+ public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+ SurfaceAnimationRunner runner) {
+ return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 408ea6eb8c1a..c3de4d5acc21 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -49,7 +49,8 @@ import java.util.function.Supplier;
* {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
* animation will be invoked, at which we reparent the children back to the original parent.
*/
-class SurfaceAnimator {
+@VisibleForTesting
+public class SurfaceAnimator {
private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
@@ -617,7 +618,8 @@ class SurfaceAnimator {
* Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
* component that is running the animation when the animation is finished.
*/
- interface OnAnimationFinishedCallback {
+ @VisibleForTesting
+ public interface OnAnimationFinishedCallback {
void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 83856153a709..016b0ff55826 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,6 +199,7 @@ import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.uri.NeededUriGrants;
+import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -2858,7 +2859,8 @@ class Task extends TaskFragment {
}
/** Bounds of the task to be used for dimming, as well as touch related tests. */
- void getDimBounds(Rect out) {
+ @Override
+ void getDimBounds(@NonNull Rect out) {
if (isRootTask()) {
getBounds(out);
return;
@@ -3306,7 +3308,8 @@ class Task extends TaskFragment {
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || isTranslucent(null)) {
+ if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+ || isTranslucent(null)) {
return super.getDimmer();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825d8bea..7c5bc6e3bb28 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -209,7 +210,26 @@ class TaskFragment extends WindowContainer<WindowContainer> {
*/
int mMinHeight;
- Dimmer mDimmer = new Dimmer(this);
+ Dimmer mDimmer = Flags.dimmerRefactor()
+ ? new SmoothDimmer(this) : new LegacyDimmer(this);
+
+ /** Apply the dim layer on the embedded TaskFragment. */
+ static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
+
+ /** Apply the dim layer on the parent Task for an embedded TaskFragment. */
+ static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1;
+
+ /**
+ * The type of dim layer area for an embedded TaskFragment.
+ */
+ @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = {
+ EMBEDDED_DIM_AREA_TASK_FRAGMENT,
+ EMBEDDED_DIM_AREA_PARENT_TASK,
+ })
+ @interface EmbeddedDimArea {}
+
+ @EmbeddedDimArea
+ private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;
/** This task fragment will be removed when the cleanup of its children are done. */
private boolean mIsRemovalRequested;
@@ -969,7 +989,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// A TaskFragment isn't translucent if it has at least one visible activity that occludes
// this TaskFragment.
return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
- starting) == null;
+ starting, true /* ignoringKeyguard */) == null;
}
/**
@@ -982,7 +1002,20 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
+ true /* ignoringKeyguard */) == null;
+ }
+
+ /**
+ * Like {@link #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the
+ * windows rather than their visibility ignoring keyguard.
+ */
+ boolean isTranslucentAndVisible() {
+ if (!isAttached() || isForceHidden() || isForceTranslucent()) {
+ return true;
+ }
+ return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null,
+ false /* ignoringKeyguard */) == null;
}
ActivityRecord getTopNonFinishingActivity() {
@@ -2929,14 +2962,27 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Override
Dimmer getDimmer() {
- // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
- if (asTask() == null) {
+ // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
+ if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {
return mDimmer;
}
return super.getDimmer();
}
+ /** Bounds to be used for dimming, as well as touch related tests. */
+ void getDimBounds(@NonNull Rect out) {
+ if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) {
+ out.set(getTask().getBounds());
+ } else {
+ out.set(getBounds());
+ }
+ }
+
+ void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) {
+ mEmbeddedDimArea = embeddedDimArea;
+ }
+
@Override
void prepareSurfaces() {
if (asTask() != null) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f3fb7c442b78..93db1caf5fdf 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -418,8 +418,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (transientRoot == null) continue;
final WindowContainer<?> rootParent = transientRoot.getParent();
if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
- final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
- .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+ final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
+ .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
occludedCount++;
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f509463c5409..de7871e3c231 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1067,12 +1067,20 @@ class TransitionController {
// legacy sync
mSyncEngine.startSyncSet(queued.mLegacySync);
}
- // Post this so that the now-playing transition logic isn't interrupted.
- mAtm.mH.post(() -> {
- synchronized (mAtm.mGlobalLock) {
- queued.mOnStartCollect.onCollectStarted(true /* deferred */);
- }
- });
+ if (queued.mTransition != null
+ && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) {
+ // SLEEP transitions are special in that they don't collect anything (in fact if they
+ // do collect things it can cause problems). So, we need to run it's onCollectStarted
+ // immediately.
+ queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+ } else {
+ // Post this so that the now-playing transition logic isn't interrupted.
+ mAtm.mH.post(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+ }
+ });
+ }
}
void moveToPlaying(Transition transition) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 00b9b4c490a4..5b9acb2f67c4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -45,5 +45,7 @@ class WindowManagerFlags {
final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
+ final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
+
/* End Available Flags */
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f339d249e272..9663f3aba7f1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2261,6 +2261,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ final boolean wasTrustedOverlay = win.isWindowTrustedOverlay();
flagChanges = win.mAttrs.flags ^ attrs.flags;
privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
attrChanges = win.mAttrs.copyFrom(attrs);
@@ -2273,6 +2274,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (layoutChanged && win.providesDisplayDecorInsets()) {
configChanged = displayPolicy.updateDecorInsetsInfo();
}
+ if (wasTrustedOverlay != win.isWindowTrustedOverlay()) {
+ win.updateTrustedOverlay();
+ }
if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
|| (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -5299,7 +5303,11 @@ public class WindowManagerService extends IWindowManager.Stub
public void displayReady() {
synchronized (mGlobalLock) {
if (mMaxUiWidth > 0) {
- mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));
+ mRoot.forAllDisplays(dc -> {
+ if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) {
+ dc.setMaxUiWidth(mMaxUiWidth);
+ }
+ });
}
applyForcedPropertiesForDefaultDisplay();
mAnimator.ready();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4beec2bc79e6..7f36aec69480 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -186,6 +186,7 @@ import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
+import android.app.servertransaction.WindowStateResizeItem;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -1188,7 +1189,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- public boolean isWindowTrustedOverlay() {
+ @Override
+ void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
+ super.setInitialSurfaceControlProperties(b);
+ if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
+ getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
+ }
+ }
+
+ void updateTrustedOverlay() {
+ mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
+ isWindowTrustedOverlay());
+ }
+
+ boolean isWindowTrustedOverlay() {
return InputMonitor.isTrustedOverlay(mAttrs.type)
|| ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
&& mSession.mCanAddInternalSystemWindow)
@@ -2755,12 +2769,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// bounds, as they would be used to display the dim layer.
final TaskFragment taskFragment = getTaskFragment();
if (taskFragment != null) {
- final Task task = taskFragment.asTask();
- if (task != null) {
- task.getDimBounds(mTmpRect);
- } else {
- mTmpRect.set(taskFragment.getBounds());
- }
+ taskFragment.getDimBounds(mTmpRect);
} else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
@@ -3732,30 +3741,44 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
markRedrawForSyncReported();
- try {
- mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
- getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
- if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
- .getMergedConfiguration().windowConfiguration.getRotation()) {
- mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Requested redraw for orientation change: %s", this);
- }
-
- if (mWmService.mAccessibilityController.hasCallbacks()) {
- mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
+ if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+ getProcess().scheduleClientTransactionItem(
+ WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
+ mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
+ alwaysConsumeSystemBars, displayId,
+ syncWithBuffers ? mSyncSeqId : -1, isDragResizing));
+ onResizePostDispatched(drawPending, prevRotation, displayId);
+ } else {
+ // TODO(b/301870955): cleanup after launch
+ try {
+ mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
+ getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
+ syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+ onResizePostDispatched(drawPending, prevRotation, displayId);
+ } catch (RemoteException e) {
+ // Cancel orientation change of this window to avoid blocking unfreeze display.
+ setOrientationChanging(false);
+ mLastFreezeDuration = (int) (SystemClock.elapsedRealtime()
+ - mWmService.mDisplayFreezeTime);
+ Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
}
- } catch (RemoteException e) {
- // Cancel orientation change of this window to avoid blocking unfreeze display.
- setOrientationChanging(false);
- mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) {
+ if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
+ .getMergedConfiguration().windowConfiguration.getRotation()) {
+ mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Requested redraw for orientation change: %s", this);
+ }
+
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
+ mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
+ }
+ }
+
boolean inRelaunchingActivity() {
return mActivityRecord != null && mActivityRecord.isRelaunching();
}
@@ -5190,9 +5213,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
- if (surfaceTrustedOverlay()) {
- getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
- }
}
super.prepareSurfaces();
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index bc70658a06c4..709d5e39463c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -70,7 +70,7 @@ cc_library_static {
"com_android_server_UsbHostManager.cpp",
"com_android_server_vibrator_VibratorController.cpp",
"com_android_server_vibrator_VibratorManagerService.cpp",
- "com_android_server_PersistentDataBlockService.cpp",
+ "com_android_server_pdb_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
"com_android_server_sensor_SensorService.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 7e8ce60b8a50..0e45f61cbfe1 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -20,6 +20,7 @@ per-file com_android_server_lights_* = file:/services/core/java/com/android/serv
per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
per-file com_android_server_locksettings_* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file com_android_server_net_* = file:/services/core/java/com/android/server/net/OWNERS
+per-file com_android_server_pdb_* = file:/services/core/java/com/android/server/pdb/OWNERS
per-file com_android_server_pm_* = file:/services/core/java/com/android/server/pm/OWNERS
per-file com_android_server_power_* = file:/services/core/java/com/android/server/power/OWNERS
per-file com_android_server_powerstats_* = file:/services/core/java/com/android/server/powerstats/OWNERS
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
deleted file mode 100644
index 97e69fb4bb40..000000000000
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android_runtime/AndroidRuntime.h>
-#include <nativehelper/JNIHelp.h>
-#include <jni.h>
-#include <nativehelper/ScopedUtfChars.h>
-
-#include <utils/misc.h>
-#include <sys/ioctl.h>
-#include <sys/mount.h>
-#include <utils/Log.h>
-
-
-#include <inttypes.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-
-namespace android {
-
- uint64_t get_block_device_size(int fd)
- {
- uint64_t size = 0;
- int ret;
-
- ret = ioctl(fd, BLKGETSIZE64, &size);
-
- if (ret)
- return 0;
-
- return size;
- }
-
- int wipe_block_device(int fd)
- {
- uint64_t range[2];
- int ret;
- uint64_t len = get_block_device_size(fd);
-
- range[0] = 0;
- range[1] = len;
-
- if (range[1] == 0)
- return 0;
-
- ret = ioctl(fd, BLKSECDISCARD, &range);
- if (ret < 0) {
- ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
- range[0] = 0;
- range[1] = len;
- ret = ioctl(fd, BLKDISCARD, &range);
- if (ret < 0) {
- ALOGE("Discard failed: %s\n", strerror(errno));
- return -1;
- } else {
- ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
- return 0;
- }
-
- }
-
- return ret;
- }
-
- static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
- {
- ScopedUtfChars path(env, jpath);
- int fd = open(path.c_str(), O_RDONLY);
-
- if (fd < 0)
- return 0;
-
- const uint64_t size = get_block_device_size(fd);
-
- close(fd);
-
- return size;
- }
-
- static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
- ScopedUtfChars path(env, jpath);
- int fd = open(path.c_str(), O_WRONLY);
-
- if (fd < 0)
- return 0;
-
- const int ret = wipe_block_device(fd);
-
- close(fd);
-
- return ret;
- }
-
- static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
- {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
- };
-
- int register_android_server_PersistentDataBlockService(JNIEnv* env)
- {
- return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService",
- sMethods, NELEM(sMethods));
- }
-
-} /* namespace android */ \ No newline at end of file
diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
new file mode 100644
index 000000000000..1e3cfd049e65
--- /dev/null
+++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_runtime/AndroidRuntime.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+namespace android {
+
+uint64_t get_block_device_size(int fd) {
+ uint64_t size = 0;
+ int ret;
+
+ ret = ioctl(fd, BLKGETSIZE64, &size);
+
+ if (ret) return 0;
+
+ return size;
+}
+
+int wipe_block_device(int fd) {
+ uint64_t range[2];
+ int ret;
+ uint64_t len = get_block_device_size(fd);
+
+ range[0] = 0;
+ range[1] = len;
+
+ if (range[1] == 0) return 0;
+
+ ret = ioctl(fd, BLKSECDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
+ range[0] = 0;
+ range[1] = len;
+ ret = ioctl(fd, BLKDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Discard failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+ return 0;
+ }
+ }
+
+ return ret;
+}
+
+static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env,
+ jclass,
+ jstring jpath) {
+ ScopedUtfChars path(env, jpath);
+ int fd = open(path.c_str(), O_RDONLY);
+
+ if (fd < 0) return 0;
+
+ const uint64_t size = get_block_device_size(fd);
+
+ close(fd);
+
+ return size;
+}
+
+static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass,
+ jstring jpath) {
+ ScopedUtfChars path(env, jpath);
+ int fd = open(path.c_str(), O_WRONLY);
+
+ if (fd < 0) return 0;
+
+ const int ret = wipe_block_device(fd);
+
+ close(fd);
+
+ return ret;
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J",
+ (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize},
+ {"nativeWipe", "(Ljava/lang/String;)I",
+ (void *)com_android_server_pdb_PersistentDataBlockService_wipe},
+};
+
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) {
+ return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService",
+ sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */ \ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df4489528be5..11734da5b1ac 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -47,7 +47,7 @@ int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);
int register_android_server_tv_TvUinputBridge(JNIEnv* env);
int register_android_server_tv_TvInputHal(JNIEnv* env);
-int register_android_server_PersistentDataBlockService(JNIEnv* env);
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env);
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
@@ -108,7 +108,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_BatteryStatsService(env);
register_android_server_tv_TvUinputBridge(env);
register_android_server_tv_TvInputHal(env);
- register_android_server_PersistentDataBlockService(env);
+ register_android_server_pdb_PersistentDataBlockService(env);
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 68e2c9a57c24..c7366173ecda 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -147,7 +147,6 @@ int JTvInputHal::removeStream(int deviceId, int streamId) {
}
int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) {
- Mutex::Autolock autoLock(&mLock);
if (!mTvInput->setTvMessageEnabled(deviceId, streamId,
static_cast<AidlTvMessageEventType>(type), enabled)
.isOk()) {
@@ -188,7 +187,7 @@ static const std::map<std::pair<AidlAudioDeviceType, std::string>, audio_devices
void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
mConnections.add(info.deviceId, KeyedVector<int, Connection>());
}
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -275,7 +274,7 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
void JTvInputHal::onDeviceUnavailable(int deviceId) {
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
for (size_t i = 0; i < connections.size(); ++i) {
removeStream(deviceId, connections.keyAt(i));
@@ -289,7 +288,7 @@ void JTvInputHal::onDeviceUnavailable(int deviceId) {
void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
for (size_t i = 0; i < connections.size(); ++i) {
removeStream(deviceId, connections.keyAt(i));
@@ -330,7 +329,7 @@ void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType
void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
sp<BufferProducerThread> thread;
{
- Mutex::Autolock autoLock(&mLock);
+ Mutex::Autolock autoLock(&mStreamLock);
KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
Connection& connection = connections.editValueFor(streamId);
if (connection.mThread == NULL) {
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index b7b4b1621512..1d8d1629f67d 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -220,7 +220,6 @@ private:
void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type,
AidlTvMessage& message, signed char data[], int dataLength);
- Mutex mLock;
Mutex mStreamLock;
jweak mThiz;
sp<Looper> mLooper;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 49af89b5f02e..5a620a3b87f5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -486,7 +486,6 @@ import com.android.server.AlarmManagerInternal;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
@@ -494,6 +493,7 @@ import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.DefaultCrossProfileIntentFilter;
import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils;
import com.android.server.pm.PackageManagerLocal;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59f1edcf309d..c26aee8af83e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@ import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
import com.android.server.pm.ApexManager;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2889c749f679..952cfc48b583 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -24,7 +24,9 @@ import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS;
import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
import static android.content.res.Resources.ID_NULL;
+
import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
+
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
@@ -1065,7 +1067,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1103,7 +1108,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1143,7 +1151,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -1179,7 +1190,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
@@ -1224,7 +1238,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1269,7 +1286,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1315,7 +1335,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1358,7 +1381,10 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 55fde00a05db..e71ea263983a 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.MANAGE_USB" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 4b124caca7d6..8cc3408ad79b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -100,6 +100,14 @@ public class DeviceStateToLayoutMapTest {
}
@Test
+ public void testPowerThrottlingMapId() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(5);
+
+ assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId());
+ assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId());
+ }
+
+ @Test
public void testRearDisplayLayout() {
Layout configLayout = mDeviceStateToLayoutMap.get(2);
@@ -133,13 +141,15 @@ public class DeviceStateToLayoutMapTest {
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
/* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
/* refreshRateZoneId= */ "zone1",
- /* refreshRateThermalThrottlingMapId= */ "rr1");
+ /* refreshRateThermalThrottlingMapId= */ "rr1",
+ /* powerThrottlingMapId= */ "power1");
testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
/* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
/* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
/* refreshRateZoneId= */ "zone2",
- /* refreshRateThermalThrottlingMapId= */ "rr2");
+ /* refreshRateThermalThrottlingMapId= */ "rr2",
+ /* powerThrottlingMapId= */ "power2");
testLayout.postProcessLocked();
assertEquals(testLayout, configLayout);
@@ -200,7 +210,8 @@ public class DeviceStateToLayoutMapTest {
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
DisplayAddress.fromPhysicalDisplayId(123L),
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null));
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null));
}
@Test
@@ -215,7 +226,8 @@ public class DeviceStateToLayoutMapTest {
mDisplayIdProducerMock, Layout.Display.POSITION_FRONT,
DisplayAddress.fromPhysicalDisplayId(987L),
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null));
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null));
}
@Test
@@ -271,7 +283,8 @@ public class DeviceStateToLayoutMapTest {
enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
/* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
}
private void setupDeviceStateToLayoutMap() throws IOException {
@@ -327,7 +340,6 @@ public class DeviceStateToLayoutMapTest {
+ "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
-
+ "<layout>\n"
+ "<state>3</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" "
@@ -338,7 +350,6 @@ public class DeviceStateToLayoutMapTest {
+ "<address>678</address>\n"
+ "</display>\n"
+ "</layout>\n"
-
+ "<layout>\n"
+ "<state>4</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
@@ -352,6 +363,20 @@ public class DeviceStateToLayoutMapTest {
+ "</display>\n"
+ "</layout>\n"
+ "<layout>\n"
+ + "<state>5</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>345</address>\n"
+ + "<position>front</position>\n"
+ + "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n"
+ + "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>678</address>\n"
+ + "<position>rear</position>\n"
+ + "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n"
+ + "</display>\n"
+ + "</layout>\n"
+
+ + "<layout>\n"
+ "<state>99</state> \n"
+ "<display enabled=\"true\" defaultDisplay=\"true\" "
+ "refreshRateZoneId=\"zone1\">\n"
@@ -361,6 +386,7 @@ public class DeviceStateToLayoutMapTest {
+ "<refreshRateThermalThrottlingMapId>"
+ "rr1"
+ "</refreshRateThermalThrottlingMapId>"
+ + "<powerThrottlingMapId>power1</powerThrottlingMapId>\n"
+ "</display>\n"
+ "<display enabled=\"false\" displayGroup=\"group1\" "
+ "refreshRateZoneId=\"zone2\">\n"
@@ -370,6 +396,7 @@ public class DeviceStateToLayoutMapTest {
+ "<refreshRateThermalThrottlingMapId>"
+ "rr2"
+ "</refreshRateThermalThrottlingMapId>"
+ + "<powerThrottlingMapId>power2</powerThrottlingMapId>\n"
+ "</display>\n"
+ "</layout>\n"
+ "</layouts>\n";
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 9ac00624b343..32e28715cc74 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -699,7 +700,7 @@ public class LocalDisplayAdapterTest {
// Turn off.
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
- 0);
+ 0, null);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
assertThat(mListener.changedDisplays.size()).isEqualTo(1);
mListener.changedDisplays.clear();
@@ -1003,7 +1004,7 @@ public class LocalDisplayAdapterTest {
// Turn on / initialize
assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1012,7 +1013,7 @@ public class LocalDisplayAdapterTest {
// HDR time!
Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
- 0);
+ 0, null);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
// Display state didn't change, no listeners should have happened
assertThat(mListener.changedDisplays.size()).isEqualTo(0);
@@ -1043,7 +1044,7 @@ public class LocalDisplayAdapterTest {
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1070,7 +1071,7 @@ public class LocalDisplayAdapterTest {
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1095,7 +1096,7 @@ public class LocalDisplayAdapterTest {
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1118,7 +1119,7 @@ public class LocalDisplayAdapterTest {
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1145,9 +1146,9 @@ public class LocalDisplayAdapterTest {
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
supportedState, 0, 0, mDisplayOffloadSession);
changeStateRunnable.run();
-
- verify(mDisplayOffloader).startOffload();
}
+
+ verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 065dd1f1f743..8b13018fc14b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -720,13 +720,15 @@ public class LogicalDisplayMapperTest {
mIdProducer, POSITION_UNKNOWN,
/* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ "concurrent");
layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
mIdProducer, POSITION_UNKNOWN,
/* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ "concurrent",
- /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ "concurrent");
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
@@ -927,7 +929,7 @@ public class LogicalDisplayMapperTest {
/* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */null);
+ /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
@@ -986,7 +988,7 @@ public class LogicalDisplayMapperTest {
layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
/* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
- /* refreshRateThermalThrottlingMapId= */ null);
+ /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null);
}
private void advanceTime(long timeMs) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 22d26224c83f..c0e0df98213f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
@@ -63,12 +64,13 @@ public class BrightnessClamperControllerTest {
@Mock
private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
@Mock
+ private DisplayManagerFlags mFlags;
+ @Mock
private BrightnessModifier mMockModifier;
@Mock
private DisplayManagerInternal.DisplayPowerRequest mMockRequest;
@Mock
private DeviceConfig.Properties mMockProperties;
-
private BrightnessClamperController mClamperController;
private TestInjector mTestInjector;
@@ -219,7 +221,7 @@ public class BrightnessClamperControllerTest {
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
- mMockDisplayDeviceData, mMockContext);
+ mMockDisplayDeviceData, mMockContext, mFlags);
}
private class TestInjector extends BrightnessClamperController.Injector {
@@ -247,7 +249,8 @@ public class BrightnessClamperControllerTest {
List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(
Handler handler,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
- BrightnessClamperController.DisplayDeviceData data) {
+ BrightnessClamperController.DisplayDeviceData data,
+ DisplayManagerFlags flags) {
mCapturedChangeListener = clamperChangeListener;
return mClampers;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
new file mode 100644
index 000000000000..b3f33ad858fe
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+import junitparams.JUnitParamsRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessPowerClamperTest {
+ private static final String TAG = "BrightnessPowerClamperTest";
+ private static final float FLOAT_TOLERANCE = 0.001f;
+
+ private static final String DISPLAY_ID = "displayId";
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+ private TestPmicMonitor mPmicMonitor;
+ private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+ new FakeDeviceConfigInterface();
+ private final TestHandler mTestHandler = new TestHandler(null);
+ private BrightnessPowerClamper mClamper;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler,
+ mMockClamperChangeListener, new TestPowerData());
+ mTestHandler.flush();
+ }
+
+ @Test
+ public void testTypeIsPower() {
+ assertEquals(BrightnessClamper.Type.POWER, mClamper.getType());
+ }
+
+ @Test
+ public void testNoThrottlingData() {
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testPowerThrottlingNoOngoingAnimation() throws RemoteException {
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+ mPmicMonitor.setAvgPowerConsumed(200f);
+ float expectedBrightness = 0.5f;
+ expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+ mTestHandler.flush();
+ // Assume current brightness as max, as there is no throttling.
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+ mPmicMonitor.setAvgPowerConsumed(100f);
+ expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+ mTestHandler.flush();
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testPowerThrottlingWithOngoingAnimation() throws RemoteException {
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+ mTestHandler.flush();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+
+ mPmicMonitor.setAvgPowerConsumed(200f);
+ float expectedBrightness = 0.5f;
+ expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+ mTestHandler.flush();
+ // Assume current brightness as max, as there is no throttling.
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
+
+ mPmicMonitor.setAvgPowerConsumed(100f);
+ expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
+ mTestHandler.flush();
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException {
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
+ mTestHandler.flush();
+ assertFalse(mClamper.isActive());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+ // update a new device config for power-throttling.
+ mClamper.onDisplayChanged(new TestPowerData(
+ List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
+
+ mPmicMonitor.setAvgPowerConsumed(200f);
+ float expectedBrightness = 0.5f;
+ expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+
+ mTestHandler.flush();
+
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE);
+
+ mPmicMonitor.setAvgPowerConsumed(100f);
+ // No cap applied for Temperature.THROTTLING_NONE
+ expectedBrightness = PowerManager.BRIGHTNESS_MAX;
+ mTestHandler.flush();
+
+ // clamper should not be active anymore.
+ assertFalse(mClamper.isActive());
+ // Assume current brightness as max, as there is no throttling.
+ assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+ }
+
+
+ private static class TestPmicMonitor extends PmicMonitor {
+ private Temperature mCurrentTemperature;
+ private final PowerChangeListener mListener;
+ TestPmicMonitor(PowerChangeListener listener, int pollingTime) {
+ super(listener, pollingTime);
+ mListener = listener;
+ }
+ public void setAvgPowerConsumed(float power) {
+ int status = mCurrentTemperature.getStatus();
+ mListener.onChanged(power, status);
+ }
+ public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
+ mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
+ }
+ }
+
+ private class TestInjector extends BrightnessPowerClamper.Injector {
+ @Override
+ TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
+ int pollingTime) {
+ mPmicMonitor = new TestPmicMonitor(listener, pollingTime);
+ return mPmicMonitor;
+ }
+
+ @Override
+ DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+ return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+ }
+ }
+
+ private static class TestPowerData implements BrightnessPowerClamper.PowerData {
+
+ private final String mUniqueDisplayId;
+ private final String mDataId;
+ private final PowerThrottlingData mData;
+ private final PowerThrottlingConfigData mConfigData;
+
+ private TestPowerData() {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+ }
+
+ private TestPowerData(List<ThrottlingLevel> data) {
+ this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+ }
+
+ private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+ mUniqueDisplayId = uniqueDisplayId;
+ mDataId = dataId;
+ mData = PowerThrottlingData.create(data);
+ mConfigData = new PowerThrottlingConfigData(0.1f, 10);
+ }
+
+ @NonNull
+ @Override
+ public String getUniqueDisplayId() {
+ return mUniqueDisplayId;
+ }
+
+ @NonNull
+ @Override
+ public String getPowerThrottlingDataId() {
+ return mDataId;
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingData getPowerThrottlingData() {
+ return mData;
+ }
+
+ @Nullable
+ @Override
+ public PowerThrottlingConfigData getPowerThrottlingConfigData() {
+ return mConfigData;
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index c7c09b5deb35..ec27f9d220dc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -44,12 +44,12 @@ import android.provider.Settings.System;
import android.test.mock.MockContentResolver;
import android.view.Display;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -57,6 +57,7 @@ import com.android.server.twilight.TwilightState;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -90,9 +91,13 @@ public class ColorDisplayServiceTest {
ColorDisplayManager.COLOR_MODE_BOOSTED,
};
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
@Before
public void setUp() {
- mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ mContext = Mockito.spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
doReturn(mContext).when(mContext).getApplicationContext();
final Resources res = Mockito.spy(mContext.getResources());
@@ -112,43 +117,36 @@ public class ColorDisplayServiceTest {
doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
mTwilightManager = new MockTwilightManager();
- LocalServices.addService(TwilightManager.class, mTwilightManager);
+ mLocalServiceKeeperRule.overrideLocalService(TwilightManager.class, mTwilightManager);
mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
- LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+ mLocalServiceKeeperRule.overrideLocalService(
+ DisplayTransformManager.class, mDisplayTransformManager);
mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ DisplayManagerInternal.class, mDisplayManagerInternal);
mCds = new ColorDisplayService(mContext);
mBinderService = mCds.new BinderService();
- LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mLocalServiceKeeperRule.overrideLocalService(
+ ColorDisplayService.ColorDisplayServiceInternal.class,
mCds.new ColorDisplayServiceInternal());
}
@After
public void tearDown() {
- /*
- * Wait for internal {@link Handler} to finish processing pending messages, so that test
- * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
- */
- mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
+ // synchronously cancel all animations
+ mCds.mHandler.runWithScissors(() -> mCds.cancelAllAnimators(), /* timeout */ 1000);
mCds = null;
- LocalServices.removeServiceForTest(TwilightManager.class);
mTwilightManager = null;
- LocalServices.removeServiceForTest(DisplayTransformManager.class);
-
mUserId = UserHandle.USER_NULL;
mContext = null;
FakeSettingsProvider.clearSettingsProvider();
-
- LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
}
@Test
@@ -1249,10 +1247,10 @@ public class ColorDisplayServiceTest {
private void startService() {
Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
- mCds.onUserChanged(mUserId);
- });
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED));
+ // onUserChanged cancels running animations, and should be called in handler thread
+ mCds.mHandler.runWithScissors(() -> mCds.onUserChanged(mUserId), 1000);
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 5b519633081a..21e3b34ed61a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -62,8 +62,6 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
@@ -75,7 +73,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_W
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
@@ -85,7 +82,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INT
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
-import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
@@ -799,47 +795,6 @@ public final class AlarmManagerServiceTest {
}
@Test
- public void updatingExactAlarmDenyList() {
- ArraySet<String> denyListed = new ArraySet<>(new String[]{
- "com.example.package1",
- "com.example.package2",
- "com.example.package3",
- });
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
- "com.example.package1,com.example.package2,com.example.package3");
- assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
-
- denyListed = new ArraySet<>(new String[]{
- "com.example.package1",
- "com.example.package4",
- });
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST,
- "com.example.package1,com.example.package4");
- assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST);
-
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
- assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size());
- }
-
- @Test
- public void exactAlarmDenyListMaxSize() {
- final ArraySet<String> expectedSet = new ArraySet<>();
- final StringBuilder sb = new StringBuilder("package1");
- expectedSet.add("package1");
- for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) {
- sb.append(",package");
- sb.append(i);
- if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) {
- expectedSet.add("package" + i);
- }
- }
- assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size());
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString());
- assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST);
- }
-
- @Test
public void positiveWhileIdleQuotas() {
setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);
assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
@@ -2212,50 +2167,30 @@ public final class AlarmManagerServiceTest {
}
@Test
- public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException {
+ public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_IGNORED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
@Test
- public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
- assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- }
-
- @Test
public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT);
- assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
-
- mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
- mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));
}
@@ -2281,41 +2216,33 @@ public final class AlarmManagerServiceTest {
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
// No permission, no exemption.
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
- assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
- // No permission, no exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// Policy permission only, no exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mockUseExactAlarmState(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
mockUseExactAlarmState(false);
// User permission only, no exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT);
- assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
-
- // User permission only, no exemption.
- mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// No permission, exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);
doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
// Both permissions and exemption.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
mockUseExactAlarmState(true);
assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));
}
@@ -2514,17 +2441,12 @@ public final class AlarmManagerServiceTest {
assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
- private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) {
+ private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) {
String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;
when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))
.thenReturn(requesters);
mService.refreshExactAlarmCandidates();
- if (denyList) {
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE);
- } else {
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "");
- }
when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(mode);
}
@@ -2556,7 +2478,7 @@ public final class AlarmManagerServiceTest {
public void alarmClockBinderCallWithoutPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2630,7 +2552,7 @@ public final class AlarmManagerServiceTest {
public void exactBinderCallWithAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2650,7 +2572,7 @@ public final class AlarmManagerServiceTest {
public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2676,7 +2598,7 @@ public final class AlarmManagerServiceTest {
mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
mockUseExactAlarmState(true);
- mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(false, MODE_ERRORED);
final PendingIntent alarmPi = getNewMockPendingIntent();
mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null);
@@ -2700,7 +2622,7 @@ public final class AlarmManagerServiceTest {
public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
// If permission is denied, only then allowlist will be checked.
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2726,7 +2648,7 @@ public final class AlarmManagerServiceTest {
public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
final PendingIntent alarmPi = getNewMockPendingIntent();
@@ -2836,7 +2758,7 @@ public final class AlarmManagerServiceTest {
public void binderCallWithUserAllowlist() throws RemoteException {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);
when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true);
@@ -3052,135 +2974,11 @@ public final class AlarmManagerServiceTest {
}
@Test
- public void denyListChanged() {
- mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
- when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT);
-
- setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
-
- final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
- verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(),
- anyLong());
-
- final List<Message> messages = messageCaptor.getAllValues();
- for (final Message msg : messages) {
- assertTrue("Unwanted message sent to handler: " + msg.what,
- msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED
- || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED);
- mService.mHandler.handleMessage(msg);
- }
-
- ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"});
- verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true));
-
- ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"});
- verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false));
- }
-
- @Test
- public void permissionGrantedDueToDenyList() {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- final String[] packages = {"example.package.1", "example.package.2"};
-
- final int appId1 = 232;
- final int appId2 = 431;
-
- final int userId1 = 42;
- final int userId2 = 53;
-
- registerAppIds(packages, new Integer[]{appId1, appId2});
-
- when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
- when(mPermissionManagerInternal.getAppOpPermissionPackages(
- SCHEDULE_EXACT_ALARM)).thenReturn(packages);
- mService.refreshExactAlarmCandidates();
-
- final long allowListDuration = 53442;
- when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(
- allowListDuration);
-
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
- mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false);
-
- // No permission revoked.
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(),
- anyBoolean());
-
- // Permission got granted only for (appId1, userId2).
- final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class);
-
- verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(),
- isNull(), bundleCaptor.capture());
-
- assertEquals(userId2, userCaptor.getValue().getIdentifier());
-
- // Validate the intent.
- assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
- intentCaptor.getValue().getAction());
- assertEquals(packages[0], intentCaptor.getValue().getPackage());
-
- // Validate the options.
- final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue());
- assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
- bOptions.getTemporaryAppAllowlistType());
- assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration());
- assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED,
- bOptions.getTemporaryAppAllowlistReasonCode());
- }
-
- @Test
- public void permissionRevokedDueToDenyList() {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
-
- final String[] packages = {"example.package.1", "example.package.2"};
-
- final int appId1 = 232;
- final int appId2 = 431;
-
- final int userId1 = 42;
- final int userId2 = 53;
-
- registerAppIds(packages, new Integer[]{appId1, appId2});
-
- when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2});
-
- when(mPermissionManagerInternal.getAppOpPermissionPackages(
- SCHEDULE_EXACT_ALARM)).thenReturn(packages);
- mService.refreshExactAlarmCandidates();
-
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED);
- mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED);
-
- mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true);
-
- // Permission got revoked only for (appId1, userId2)
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true));
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true));
- verify(mService, never()).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true));
-
- verify(mService).removeExactAlarmsOnPermissionRevoked(
- eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true));
- }
-
- @Test
public void opChangedPermissionRevoked() throws Exception {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
assertAndHandleMessageSync(REMOVE_EXACT_ALARMS);
@@ -3193,20 +2991,7 @@ public final class AlarmManagerServiceTest {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
-
- mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
-
- verify(mService.mHandler, never()).sendMessageAtTime(
- argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong());
- }
-
- @Test
- public void opChangedNoPermissionChangeDueToDenyList() throws Exception {
- mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
-
- mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
@@ -3222,7 +3007,7 @@ public final class AlarmManagerServiceTest {
when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);
mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -3482,7 +3267,7 @@ public final class AlarmManagerServiceTest {
.putExtra(Intent.EXTRA_REPLACING, true);
mockUseExactAlarmState(false);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3490,7 +3275,7 @@ public final class AlarmManagerServiceTest {
assertEquals(5, mService.mAlarmStore.size());
mockUseExactAlarmState(true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3498,7 +3283,7 @@ public final class AlarmManagerServiceTest {
assertEquals(5, mService.mAlarmStore.size());
mockUseExactAlarmState(false);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);
assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE);
@@ -3653,16 +3438,16 @@ public final class AlarmManagerServiceTest {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
- mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT);
- assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);
+ assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
- mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
}
@@ -3671,11 +3456,11 @@ public final class AlarmManagerServiceTest {
mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);
mockScheduleExactAlarmState(true);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
mockScheduleExactAlarmState(false);
- mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED);
+ mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);
assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
new file mode 100644
index 000000000000..2003d04e1dbc
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.ParseException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatedPowerStatsTest {
+ private static final int TEST_POWER_COMPONENT = 1077;
+ private static final int APP_1 = 27;
+ private static final int APP_2 = 42;
+
+ private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+ private PowerStats.Descriptor mPowerComponentDescriptor;
+
+ @Before
+ public void setup() throws ParseException {
+ mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+
+ mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3,
+ PersistableBundle.forPair("speed", "fast"));
+ }
+
+ @Test
+ public void aggregation() {
+ AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+ verifyAggregatedPowerStats(stats);
+ }
+
+ @Test
+ public void xmlPersistence() throws Exception {
+ AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(baos, "UTF-8");
+ stats.writeXml(serializer);
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8");
+ AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser,
+ mAggregatedPowerStatsConfig);
+
+ verifyAggregatedPowerStats(actualStats);
+ }
+
+ private AggregatedPowerStats prepareAggregatePowerStats() {
+ AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ stats.addClockUpdate(1000, 456);
+ stats.setDuration(789);
+
+ stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000);
+ stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_CACHED, 2000);
+ stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000);
+
+ PowerStats ps = new PowerStats(mPowerComponentDescriptor);
+ ps.stats[0] = 100;
+ ps.stats[1] = 987;
+
+ ps.uidStats.put(APP_1, new long[]{389, 0, 739});
+ ps.uidStats.put(APP_2, new long[]{278, 314, 628});
+
+ stats.addPowerStats(ps, 3000);
+
+ stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000);
+ stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+ ps.stats[0] = 444;
+ ps.stats[1] = 0;
+
+ ps.uidStats.put(APP_1, new long[]{0, 0, 400});
+ ps.uidStats.put(APP_2, new long[]{100, 200, 300});
+
+ stats.addPowerStats(ps, 5000);
+
+ return stats;
+ }
+
+ private void verifyAggregatedPowerStats(AggregatedPowerStats stats) {
+ PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT)
+ .getPowerStatsDescriptor();
+ assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT);
+ assertThat(descriptor.name).isEqualTo("fan");
+ assertThat(descriptor.statsArrayLength).isEqualTo(2);
+ assertThat(descriptor.uidStatsArrayLength).isEqualTo(3);
+ assertThat(descriptor.extras.getString("speed")).isEqualTo("fast");
+
+ assertThat(getDeviceStats(stats,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+ .isEqualTo(new long[]{322, 987});
+
+ assertThat(getDeviceStats(stats,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+ .isEqualTo(new long[]{222, 0});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+ .isEqualTo(new long[]{259, 0, 492});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_CACHED))
+ .isEqualTo(new long[]{129, 0, 446});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_CACHED))
+ .isEqualTo(new long[]{0, 0, 200});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+ .isEqualTo(new long[]{185, 209, 418});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .isEqualTo(new long[]{142, 204, 359});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .isEqualTo(new long[]{50, 100, 150});
+ }
+
+ private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) {
+ long[] out = new long[states.length];
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states);
+ return out;
+ }
+
+ private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) {
+ long[] out = new long[states.length];
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states);
+ return out;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 5a2d2e3d33fd..663af5da48d2 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -42,6 +42,7 @@ import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
@@ -214,6 +215,7 @@ public class BatteryExternalStatsWorkerTest {
public class TestBatteryStatsImpl extends BatteryStatsImpl {
public TestBatteryStatsImpl(Context context) {
+ super(Clock.SYSTEM_CLOCK, null);
mPowerProfile = new PowerProfile(context, true /* forTest */);
SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 77124d0120f7..abb3be7b215b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -113,15 +113,15 @@ public class BatteryStatsHistoryIteratorTest {
public void constrainedIteration() {
prepareHistory();
- // Initial time is 3000
+ // Initial time is 1000_000
assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
- 3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
- assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
- 1003_000L, 2003_000L, 2004_000L);
- assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
- 3_000L, 3_000L, 1003_000L);
+ 1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0),
+ 2000_000L, 3000_000L, 3001_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L),
+ 1000_000L, 1000_000L, 2000_000L);
assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
- 1003_000L, 2003_000L);
+ 2000_000L);
}
private void prepareHistory() {
@@ -144,7 +144,7 @@ public class BatteryStatsHistoryIteratorTest {
ArrayList<Long> actualTimestamps = new ArrayList<>();
while (iterator.hasNext()) {
BatteryStats.HistoryItem item = iterator.next();
- actualTimestamps.add(item.currentTime);
+ actualTimestamps.add(item.time);
}
assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index f22296a6261c..1dd499c99c0c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -41,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import org.junit.Before;
@@ -69,6 +70,7 @@ public class BatteryStatsHistoryTest {
private File mSystemDir;
private File mHistoryDir;
private final MockClock mClock = new MockClock();
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
private BatteryStatsHistory mHistory;
private BatteryStats.HistoryPrinter mHistoryPrinter;
@Mock
@@ -94,7 +96,7 @@ public class BatteryStatsHistoryTest {
mClock.realtime = 123;
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- mStepDetailsCalculator, mClock, mTracer) {
+ mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {
@Override
public boolean readFileToParcel(Parcel out, AtomicFile file) {
mReadFiles.add(file.getBaseFile().getName());
@@ -210,7 +212,7 @@ public class BatteryStatsHistoryTest {
mClock.realtime = 1000 * i;
fileList.add(mClock.realtime + ".bh");
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
verifyFileNames(mHistory, fileList);
verifyActiveFile(mHistory, mClock.realtime + ".bh");
@@ -218,7 +220,7 @@ public class BatteryStatsHistoryTest {
// create file 32
mClock.realtime = 1000 * 32;
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
fileList.add("32000.bh");
fileList.remove(0);
@@ -229,7 +231,7 @@ public class BatteryStatsHistoryTest {
// create file 33
mClock.realtime = 1000 * 33;
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
// verify file 1 is deleted
fileList.add("33000.bh");
@@ -240,7 +242,7 @@ public class BatteryStatsHistoryTest {
// create a new BatteryStatsHistory object, it will pick up existing history files.
BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- null, mClock, mTracer);
+ null, mClock, mMonotonicClock, mTracer);
// verify constructor can pick up all files from file system.
verifyFileNames(history2, fileList);
verifyActiveFile(history2, "33000.bh");
@@ -262,7 +264,7 @@ public class BatteryStatsHistoryTest {
// create file 1.
mClock.realtime = 2345678;
- history2.startNextFile();
+ history2.startNextFile(mClock.realtime);
createActiveFile(history2);
verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
verifyActiveFile(history2, "2345678.bh");
@@ -336,14 +338,14 @@ public class BatteryStatsHistoryTest {
mHistory.recordEvent(mClock.realtime, mClock.uptime,
BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
- mHistory.startNextFile(); // 1000.bh
+ mHistory.startNextFile(mClock.realtime); // 1000.bh
mClock.realtime = 2000;
mClock.uptime = 2000;
mHistory.recordEvent(mClock.realtime, mClock.uptime,
BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
- mHistory.startNextFile(); // 2000.bh
+ mHistory.startNextFile(mClock.realtime); // 2000.bh
mClock.realtime = 3000;
mClock.uptime = 3000;
@@ -351,7 +353,7 @@ public class BatteryStatsHistoryTest {
HistoryItem.EVENT_ALARM, "alarm", 42);
// Flush accumulated history to disk
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
}
private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -518,7 +520,7 @@ public class BatteryStatsHistoryTest {
// Keep the preserved part of history short - we only need to capture the very tail of
// history.
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000,
- mStepDetailsCalculator, mClock, mTracer);
+ mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
mHistory.forceRecordAllHistory();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 5df0acb65249..b1da1fc88378 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import org.junit.Rule;
@@ -64,6 +65,8 @@ public class BatteryUsageStatsProviderTest {
new BatteryUsageStatsRule(12345, mHistoryDir)
.setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
.setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+ private MockClock mMockClock = mStatsRule.getMockClock();
+
@Test
public void test_getBatteryUsageStats() {
BatteryStatsImpl batteryStats = prepareBatteryStats();
@@ -369,17 +372,23 @@ public class BatteryUsageStatsProviderTest {
public void testAggregateBatteryStats() {
Context context = InstrumentationRegistry.getContext();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- mStatsRule.setCurrentTime(5 * MINUTE_IN_MS);
+ MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+
+ setTime(5 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
- BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context,
- batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
- new TestHandler(), Integer.MAX_VALUE);
- batteryUsageStatsStore.onSystemReady();
+
+ PowerStatsStore powerStatsStore = new PowerStatsStore(
+ new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
+ new TestHandler(), null);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
- batteryStats, batteryUsageStatsStore);
+ batteryStats, powerStatsStore);
+
+ batteryStats.setBatteryResetListener(reason ->
+ powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(),
+ provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -389,7 +398,7 @@ public class BatteryUsageStatsProviderTest {
batteryStats.noteFlashlightOffLocked(APP_UID,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(25 * MINUTE_IN_MS);
+ setTime(25 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -402,7 +411,7 @@ public class BatteryUsageStatsProviderTest {
batteryStats.noteFlashlightOffLocked(APP_UID,
50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(55 * MINUTE_IN_MS);
+ setTime(55 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -416,7 +425,7 @@ public class BatteryUsageStatsProviderTest {
batteryStats.noteFlashlightOffLocked(APP_UID,
70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(75 * MINUTE_IN_MS);
+ setTime(75 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -430,7 +439,7 @@ public class BatteryUsageStatsProviderTest {
batteryStats.noteFlashlightOffLocked(APP_UID,
90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(95 * MINUTE_IN_MS);
+ setTime(95 * MINUTE_IN_MS);
// Include the first and the second snapshot, but not the third or current
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
@@ -457,29 +466,41 @@ public class BatteryUsageStatsProviderTest {
.of(180.0);
}
+ private void setTime(long timeMs) {
+ mMockClock.currentTime = timeMs;
+ mMockClock.realtime = timeMs;
+ }
+
@Test
public void testAggregateBatteryStats_incompatibleSnapshot() {
Context context = InstrumentationRegistry.getContext();
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
- BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
-
- when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
- .thenReturn(new long[]{1000, 2000});
+ PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
- when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+ PowerStatsSpan span0 = new PowerStatsSpan(0);
+ span0.addTimeFrame(0, 1000, 1234);
+ span0.addSection(new BatteryUsageStatsSection(
new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
- .setStatsDuration(1234).build());
+ .setStatsDuration(1234).build()));
- // Add a snapshot, with a different set of custom power components. It should
- // be skipped by the aggregation.
- when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+ PowerStatsSpan span1 = new PowerStatsSpan(1);
+ span1.addTimeFrame(0, 2000, 4321);
+ span1.addSection(new BatteryUsageStatsSection(
new BatteryUsageStats.Builder(new String[]{"different"})
- .setStatsDuration(4321).build());
+ .setStatsDuration(4321).build()));
+
+ when(powerStatsStore.getTableOfContents()).thenReturn(
+ List.of(span0.getMetadata(), span1.getMetadata()));
+
+ when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE))
+ .thenReturn(span0);
+ when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
+ .thenReturn(span1);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
- batteryStats, batteryUsageStatsStore);
+ batteryStats, powerStatsStore);
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
.aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 93cbea6125fa..3579fce11c8d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -88,6 +88,10 @@ public class BatteryUsageStatsRule implements TestRule {
mBatteryStats.onSystemReady();
}
+ public MockClock getMockClock() {
+ return mMockClock;
+ }
+
public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
mPowerProfile.forceInitForTesting(mContext, xmlId);
return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
deleted file mode 100644
index b846e3a36656..000000000000
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.os.BatteryManager;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Xml;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.PowerProfile;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SuppressWarnings("GuardedBy")
-public class BatteryUsageStatsStoreTest {
- private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
-
- private final MockClock mMockClock = new MockClock();
- private MockBatteryStatsImpl mBatteryStats;
- private BatteryUsageStatsStore mBatteryUsageStatsStore;
- private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
- private File mStoreDirectory;
-
- @Before
- public void setup() {
- mMockClock.currentTime = 123;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock);
- mBatteryStats.setNoAutoReset(true);
- mBatteryStats.setPowerProfile(mock(PowerProfile.class));
- mBatteryStats.onSystemReady();
-
- Context context = InstrumentationRegistry.getContext();
-
- mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest");
- clearDirectory(mStoreDirectory);
-
- mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats,
- mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- mBatteryUsageStatsStore.onSystemReady();
-
- mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
- }
-
- @Test
- public void testStoreSnapshot() {
- mMockClock.currentTime = 1_600_000;
- mMockClock.realtime = 1000;
- mMockClock.uptime = 1000;
-
- prepareBatteryStats();
-
- mMockClock.realtime = 1_000_000;
- mMockClock.uptime = 1_000_000;
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- assertThat(timestamps).hasLength(1);
- assertThat(timestamps[0]).isEqualTo(1_600_000);
-
- final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats(
- 1_600_000);
- assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123);
- assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
- assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
- assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
- assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
- assertThat(batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
- .isEqualTo(600); // (3_600_000 - 3_000_000) / 1000
- }
-
- @Test
- public void testGarbageCollectOldSnapshots() throws Exception {
- prepareBatteryStats();
-
- mMockClock.realtime = 10_000_000;
- mMockClock.uptime = 10_000_000;
- mMockClock.currentTime = 10_000_000;
-
- final int snapshotFileSize = getSnapshotFileSize();
- final int numberOfSnapshots =
- (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize);
- for (int i = 0; i < numberOfSnapshots + 2; i++) {
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
- mMockClock.realtime += 10_000_000;
- mMockClock.uptime += 10_000_000;
- mMockClock.currentTime += 10_000_000;
- prepareBatteryStats();
- }
-
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- Arrays.sort(timestamps);
- assertThat(timestamps).hasLength(numberOfSnapshots);
- // Two snapshots (10_000_000 and 20_000_000) should have been discarded
- assertThat(timestamps[0]).isEqualTo(30_000_000);
- assertThat(getDirectorySize(mStoreDirectory))
- .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- }
-
- @Test
- public void testRemoveAllSnapshots() throws Exception {
- prepareBatteryStats();
-
- for (int i = 0; i < 3; i++) {
- mMockClock.realtime += 10_000_000;
- mMockClock.uptime += 10_000_000;
- mMockClock.currentTime += 10_000_000;
- prepareBatteryStats();
-
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
- }
-
- assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
-
- mBatteryUsageStatsStore.removeAllSnapshots();
-
- assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
- }
-
- @Test
- public void testSavingStatsdAtomPullTimestamp() {
- mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
- assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
- .isEqualTo(1234);
- mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
- assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
- .isEqualTo(5478);
- }
-
- private void prepareBatteryStats() {
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
- /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
- mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime);
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
- /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
- mMockClock.realtime + 500_000, mMockClock.uptime + 500_000,
- mMockClock.currentTime + 500_000);
- }
-
- private void clearDirectory(File dir) {
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- clearDirectory(child);
- }
- child.delete();
- }
- }
- }
-
- private long getDirectorySize(File dir) {
- long size = 0;
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- size += getDirectorySize(child);
- } else {
- size += child.length();
- }
- }
- }
- return size;
- }
-
- private int getSnapshotFileSize() throws IOException {
- BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats(
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .build());
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- stats.writeXml(serializer);
- serializer.endDocument();
- return out.toByteArray().length;
- }
-
- private static class TestHandler extends Handler {
- TestHandler() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- msg.getCallback().run();
- return true;
- }
- }
-}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 4ecee9fe7d23..30a731818f36 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -122,7 +122,7 @@ public class MultiStateStatsTest {
// 4 * 10 = 40 bits needed to represent the composite state
MultiStateStats.States[] states = new MultiStateStats.States[10];
for (int i = 0; i < states.length; i++) {
- states[i] = new MultiStateStats.States(true, labels);
+ states[i] = new MultiStateStats.States("foo", true, labels);
}
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
@@ -191,8 +191,8 @@ public class MultiStateStatsTest {
private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
boolean trackProcState, boolean trackScreenState) {
return new MultiStateStats.Factory(DIMENSION_COUNT,
- new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
- new MultiStateStats.States(trackProcState,
+ new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"),
+ new MultiStateStats.States("ps", trackProcState,
BatteryConsumer.processStateToString(
BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
BatteryConsumer.processStateToString(
@@ -203,7 +203,7 @@ public class MultiStateStatsTest {
BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
BatteryConsumer.processStateToString(
BatteryConsumer.PROCESS_STATE_CACHED)),
- new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+ new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));
}
private FactorySubject assertThatCpuPerformanceStatsFactory(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 47de44324ae1..b52fc8a7c727 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,11 +24,14 @@ import static org.mockito.Mockito.mock;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.PersistableBundle;
+import android.text.format.DateFormat;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import org.junit.Before;
@@ -36,16 +39,19 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PowerStatsAggregatorTest {
private static final int TEST_POWER_COMPONENT = 77;
private static final int TEST_UID = 42;
+ private static final long START_TIME = 1234;
private final MockClock mClock = new MockClock();
- private long mStartTime;
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);
private BatteryStatsHistory mHistory;
private PowerStatsAggregator mAggregator;
private int mAggregatedStatsCount;
@@ -53,25 +59,25 @@ public class PowerStatsAggregatorTest {
@Before
public void setup() throws ParseException {
mHistory = new BatteryStatsHistory(32, 1024,
- mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
- mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
- .parse("2008-09-23 08:00").getTime();
- mClock.currentTime = mStartTime;
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
+ mMonotonicClock);
- PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
- builder.trackPowerComponent(TEST_POWER_COMPONENT)
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(TEST_POWER_COMPONENT)
.trackDeviceStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN)
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
.trackUidStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN,
- PowerStatsAggregator.STATE_PROCESS_STATE);
- mAggregator = builder.build();
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ mAggregator = new PowerStatsAggregator(config, mHistory);
}
@Test
public void stateUpdates() {
+ mClock.currentTime = 1222156800000L; // An important date in world history
+
mHistory.forceRecordAllHistory();
mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
@@ -98,15 +104,32 @@ public class PowerStatsAggregatorTest {
mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- advance(3000);
+ advance(1000);
+
+ mClock.currentTime += 60 * 60 * 1000; // one hour
+ mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime);
+
+ advance(2000);
powerStats.stats = new long[]{20000};
powerStats.uidStats.put(TEST_UID, new long[]{4444});
mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
- mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ mAggregator.aggregatePowerStats(0, 0, stats -> {
assertThat(mAggregatedStatsCount++).isEqualTo(0);
- assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME);
+
+ List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+ assertThat(clockUpdates).hasSize(2);
+
+ AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0);
+ assertThat(clockUpdate0.monotonicTime).isEqualTo(1234);
+ assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00");
+
+ AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1);
+ assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000);
+ assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03");
+
assertThat(stats.getDuration()).isEqualTo(5000);
long[] values = new long[1];
@@ -115,40 +138,47 @@ public class PowerStatsAggregatorTest {
TEST_POWER_COMPONENT);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{10000});
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER}))
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}))
.isTrue();
assertThat(values).isEqualTo(new long[]{20000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1234});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1111});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
BatteryConsumer.PROCESS_STATE_BACKGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{3333});
});
}
+ @NonNull
+ private static CharSequence formatDateTime(long timeInMillis) {
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ cal.setTimeInMillis(timeInMillis);
+ return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+ }
+
@Test
public void incompatiblePowerStats() {
mHistory.forceRecordAllHistory();
@@ -181,39 +211,39 @@ public class PowerStatsAggregatorTest {
mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
- mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ mAggregator.aggregatePowerStats(0, 0, stats -> {
long[] values = new long[1];
PowerComponentAggregatedPowerStats powerComponentStats =
stats.getPowerComponentStats(TEST_POWER_COMPONENT);
if (mAggregatedStatsCount == 0) {
- assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME);
assertThat(stats.getDuration()).isEqualTo(2000);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{10000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1234});
} else if (mAggregatedStatsCount == 1) {
- assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000);
assertThat(stats.getDuration()).isEqualTo(1000);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{20000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{4444});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
new file mode 100644
index 000000000000..0e58787a6a9b
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsSchedulerTest {
+ private PowerStatsStore mPowerStatsStore;
+ private Handler mHandler;
+ private MockClock mClock = new MockClock();
+ private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+ private MockBatteryStatsImpl mBatteryStats;
+ private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private PowerStatsScheduler mPowerStatsScheduler;
+ private PowerProfile mPowerProfile;
+ private PowerStatsAggregator mPowerStatsAggregator;
+ private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+ @Before
+ public void setup() {
+ final Context context = InstrumentationRegistry.getContext();
+
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+ mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+ mClock.realtime = 7654321;
+
+ HandlerThread bgThread = new HandlerThread("bg thread");
+ bgThread.start();
+ File systemDir = context.getCacheDir();
+ mHandler = new Handler(bgThread.getLooper());
+ mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
+ mPowerProfile = mock(PowerProfile.class);
+ when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
+ mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+ mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
+ mPowerStatsAggregator = mock(PowerStatsAggregator.class);
+ mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+ TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
+ mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void storeAggregatePowerStats() {
+ mPowerStatsStore.reset();
+
+ assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+ mPowerStatsStore.storeAggregatedPowerStats(
+ createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
+ 123));
+
+ long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
+ mClock.realtime += delayBeforeAggregating;
+ mClock.currentTime += delayBeforeAggregating;
+
+ doAnswer(invocation -> {
+ // The first span is longer than 30 min, because the end time is being aligned with
+ // the wall clock. Subsequent spans should be precisely 30 minutes.
+ long startTime = invocation.getArgument(0);
+ long endTime = invocation.getArgument(1);
+ Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+ long startTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+ long endTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+ assertThat(startTime).isEqualTo(7654321 + 123);
+ assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
+ assertThat(Instant.ofEpochMilli(endTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+
+ consumer.accept(
+ createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+ return null;
+ }).doAnswer(invocation -> {
+ long startTime = invocation.getArgument(0);
+ long endTime = invocation.getArgument(1);
+ Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+ long startTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+ long endTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+ assertThat(Instant.ofEpochMilli(startTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+ assertThat(Instant.ofEpochMilli(endTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
+
+ consumer.accept(
+ createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+ return null;
+ }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
+ any(Consumer.class));
+
+ mPowerStatsScheduler.schedulePowerStatsAggregation();
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+
+ verify(mPowerStatsAggregator, times(2))
+ .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
+
+ List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+ assertThat(contents).hasSize(3);
+ // Skip the first entry, which was placed in the store at the beginning of this test
+ PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
+ PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
+ assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+ assertThat(timeFrame2.startMonotonicTime)
+ .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
+ assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+ assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
+ }
+
+ private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
+ long duration) {
+ AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ stats.addClockUpdate(monotonicTime, currentTime);
+ stats.setDuration(duration);
+ return stats;
+ }
+
+ @Test
+ public void storeBatteryUsageStatsOnReset() {
+ mBatteryStats.forceRecordAllHistory();
+ synchronized (mBatteryStats) {
+ mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true,
+ BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
+ }
+
+ mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false);
+
+ assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+ mPowerStatsScheduler.start(true);
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime);
+ }
+
+ mClock.realtime += 60000;
+ mClock.currentTime += 60000;
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime);
+ }
+
+ mClock.realtime += 60000;
+ mClock.currentTime += 60000;
+
+ // Battery stats reset should have the side-effect of saving accumulated battery usage stats
+ synchronized (mBatteryStats) {
+ mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ // Await completion
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+
+ List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+ assertThat(contents).hasSize(1);
+
+ PowerStatsSpan.Metadata metadata = contents.get(0);
+
+ PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ BatteryUsageStatsSection.TYPE);
+ assertThat(span).isNotNull();
+
+ List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
+ assertThat(timeFrames).hasSize(1);
+ assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
+ assertThat(timeFrames.get(0).duration).isEqualTo(120000);
+
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ assertThat(sections).hasSize(1);
+
+ PowerStatsSpan.Section section = sections.get(0);
+ assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
+ BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+ assertThat(bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60000);
+ }
+
+ @Test
+ public void alignToWallClock() {
+ // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT1M30S").toMillis());
+
+ // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT2M45S").toMillis());
+
+ // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT15S").toMillis());
+
+ // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec -
+ // rounded to the next 3 hours
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3),
+ 123 + TimeUnit.HOURS.toMillis(9),
+ Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT1H46M30S").toMillis());
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
new file mode 100644
index 000000000000..d3628b5888c8
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
+public class PowerStatsStoreTest {
+ private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
+
+ private PowerStatsStore mPowerStatsStore;
+ private File mStoreDirectory;
+
+ @Before
+ public void setup() {
+ Context context = InstrumentationRegistry.getContext();
+
+ mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest");
+ clearDirectory(mStoreDirectory);
+
+ mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
+ MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES,
+ new TestHandler(),
+ (sectionType, parser) -> {
+ if (sectionType.equals(TestSection.TYPE)) {
+ return TestSection.readXml(parser);
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void garbageCollectOldSpans() throws Exception {
+ int spanSize = 500;
+ final int numberOfSnaps =
+ (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize);
+ for (int i = 0; i < numberOfSnaps + 2; i++) {
+ PowerStatsSpan span = new PowerStatsSpan(i);
+ span.addSection(new TestSection(i, spanSize));
+ mPowerStatsStore.storePowerStatsSpan(span);
+ }
+
+ assertThat(getDirectorySize(mStoreDirectory))
+ .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
+
+ List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+ assertThat(toc.size()).isLessThan(numberOfSnaps);
+ int minPreservedSpanId = numberOfSnaps - toc.size();
+ for (PowerStatsSpan.Metadata metadata : toc) {
+ assertThat(metadata.getId()).isAtLeast(minPreservedSpanId);
+ }
+ }
+
+ @Test
+ public void reset() throws Exception {
+ for (int i = 0; i < 3; i++) {
+ PowerStatsSpan span = new PowerStatsSpan(i);
+ span.addSection(new TestSection(i, 42));
+ mPowerStatsStore.storePowerStatsSpan(span);
+ }
+
+ assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
+
+ mPowerStatsStore.reset();
+
+ assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
+ }
+
+ private void clearDirectory(File dir) {
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ clearDirectory(child);
+ }
+ child.delete();
+ }
+ }
+ }
+
+ private long getDirectorySize(File dir) {
+ long size = 0;
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ size += getDirectorySize(child);
+ } else {
+ size += child.length();
+ }
+ }
+ }
+ return size;
+ }
+
+ private static class TestSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "much-text";
+
+ private final int mSize;
+ private final int mValue;
+
+ TestSection(int value, int size) {
+ super(TYPE);
+ mSize = size;
+ mValue = value;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mSize; i++) {
+ sb.append("X");
+ }
+ serializer.startTag(null, "much-text");
+ serializer.attributeInt(null, "value", mValue);
+ serializer.text(sb.toString());
+ serializer.endTag(null, "much-text");
+ }
+
+ public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException {
+ TestSection section = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals("much-text"))) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) {
+ section = new TestSection(parser.getAttributeInt(null, "value"), 0);
+ }
+ }
+ return section;
+ }
+ }
+
+ private static class TestHandler extends Handler {
+ TestHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ msg.getCallback().run();
+ return true;
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 2ffe4aacda73..df46054f0f6f 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -1079,7 +1079,7 @@ public class PowerStatsServiceTest {
GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();
mService.getSupportedPowerMonitorsImpl(result);
assertThat(result.powerMonitors).isNotNull();
- assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList())
+ assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList())
.containsAtLeast(
"energyconsumer0",
"BLUETOOTH/1",
@@ -1130,7 +1130,7 @@ public class PowerStatsServiceTest {
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
Map<String, PowerMonitor> map =
Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
- .collect(Collectors.toMap(pm -> pm.name, pm -> pm));
+ .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm));
PowerMonitor consumer1 = map.get("energyconsumer0");
PowerMonitor consumer2 = map.get("BLUETOOTH/1");
PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0");
@@ -1196,6 +1196,6 @@ public class PowerStatsServiceTest {
supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();
mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);
assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors)
- .map(pm -> pm.name).toList()).contains("energyconsumer0");
+ .map(PowerMonitor::getName).toList()).contains("energyconsumer0");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 49f22eca5643..cf315a4e0b9a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@@ -53,12 +54,18 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
import android.view.Display;
@@ -93,8 +100,13 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
/**
* APCT tests for {@link AccessibilityManagerService}.
@@ -104,6 +116,10 @@ public class AccessibilityManagerServiceTest {
public final A11yTestableContext mTestableContext = new A11yTestableContext(
ApplicationProvider.getApplicationContext());
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int ACTION_ID = 20;
private static final String LABEL = "label";
private static final String INTENT_ACTION = "TESTACTION";
@@ -204,6 +220,8 @@ public class AccessibilityManagerServiceTest {
mA11yms.getCurrentUserIdLocked());
when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
mMockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+ mMockResolveInfo.serviceInfo.packageName = "packageName";
+ mMockResolveInfo.serviceInfo.name = "className";
mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient);
@@ -581,6 +599,73 @@ public class AccessibilityManagerServiceTest {
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+ // Test old behavior to validate lock detection for the old (locked access) case.
+ public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
+ setupAccessibilityServiceConnection(0);
+ final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+ when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(mMockResolveInfo));
+ when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+ final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+ packageIntent.setData(Uri.parse("test://package"));
+ packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+ packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+ mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+ assertThat(lockState.get()).containsExactly(true);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+ public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
+ setupAccessibilityServiceConnection(0);
+ final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+ when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(mMockResolveInfo));
+ when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+ final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+ packageIntent.setData(Uri.parse("test://package"));
+ packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
+ packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
+ mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
+
+ assertThat(lockState.get()).containsExactly(false);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
+ public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
+ setupAccessibilityServiceConnection(0);
+ final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
+ when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(mMockResolveInfo));
+ when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
+
+ mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1);
+
+ assertThat(lockState.get()).containsExactly(false);
+ }
+
+ // Single package intents can trigger multiple PackageMonitor callbacks.
+ // Collect the state of the lock in a set, since tests only care if calls
+ // were all locked or all unlocked.
+ private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() {
+ final AtomicReference<Set<Boolean>> lockState =
+ new AtomicReference<>(new HashSet<Boolean>());
+ doAnswer((Answer<XmlResourceParser>) invocation -> {
+ lockState.updateAndGet(set -> {
+ set.add(mA11yms.unsafeIsLockHeld());
+ return set;
+ });
+ return null;
+ }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any());
+ return lockState;
+ }
+
private void mockManageAccessibilityGranted(TestableContext context) {
context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 1cd61e90126e..efcdbd488a39 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,6 +44,10 @@ import android.content.Context;
import android.graphics.PointF;
import android.os.Looper;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -56,6 +60,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.Flags;
import com.android.server.accessibility.utils.GestureLogParser;
import com.android.server.testutils.OffsettableClock;
@@ -76,6 +81,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
+
@RunWith(AndroidJUnit4.class)
public class TouchExplorerTest {
@@ -119,6 +125,9 @@ public class TouchExplorerTest {
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
/**
* {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
* is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -161,11 +170,16 @@ public class TouchExplorerTest {
goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
// Wait for transiting to touch exploring state.
mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
- moveEachPointers(mLastEvent, p(10, 10));
- send(mLastEvent);
+ assertState(STATE_TOUCH_EXPLORING);
+ // Manually construct the next move event. Using moveEachPointers() will batch the move
+ // event which produces zero movement for some reason.
+ float[] x = new float[1];
+ float[] y = new float[1];
+ x[0] = mLastEvent.getX(0) + mTouchSlop;
+ y[0] = mLastEvent.getY(0) + mTouchSlop;
+ send(manyPointerEvent(ACTION_MOVE, x, y));
goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
- assertState(STATE_TOUCH_EXPLORING);
}
/**
@@ -173,7 +187,34 @@ public class TouchExplorerTest {
* change the coordinates.
*/
@Test
- public void testOneFingerMoveWithExtraMoveEvents() {
+ @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+ public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() {
+ goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
+ // Inject a set of move events that have the same coordinates as the down event.
+ moveEachPointers(mLastEvent, p(0, 0));
+ send(mLastEvent);
+ // Wait for transition to touch exploring state.
+ mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
+ // Now move for real.
+ moveAtLeastTouchSlop(mLastEvent);
+ send(mLastEvent);
+ // One more move event with no change.
+ moveEachPointers(mLastEvent, p(0, 0));
+ send(mLastEvent);
+ goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
+ assertCapturedEvents(
+ ACTION_HOVER_ENTER,
+ ACTION_HOVER_MOVE,
+ ACTION_HOVER_EXIT);
+ }
+
+ /**
+ * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not
+ * change the coordinates.
+ */
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
+ public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {
goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
// Inject a set of move events that have the same coordinates as the down event.
moveEachPointers(mLastEvent, p(0, 0));
@@ -181,7 +222,7 @@ public class TouchExplorerTest {
// Wait for transition to touch exploring state.
mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
// Now move for real.
- moveEachPointers(mLastEvent, p(10, 10));
+ moveAtLeastTouchSlop(mLastEvent);
send(mLastEvent);
// One more move event with no change.
moveEachPointers(mLastEvent, p(0, 0));
@@ -242,7 +283,7 @@ public class TouchExplorerTest {
moveEachPointers(mLastEvent, p(0, 0), p(0, 0));
send(mLastEvent);
// Now move for real.
- moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
+ moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));
send(mLastEvent);
goToStateClearFrom(STATE_DRAGGING_2FINGERS);
assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
@@ -251,7 +292,7 @@ public class TouchExplorerTest {
@Test
public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {
goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
- moveEachPointers(mLastEvent, p(10, 10));
+ moveAtLeastTouchSlop(mLastEvent);
send(mLastEvent);
// Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.
mHandler.fastForward(10);
@@ -277,7 +318,7 @@ public class TouchExplorerTest {
// Wait for the finger moving to the second view.
mHandler.fastForward(oneThirdUserIntentTimeout);
- moveEachPointers(mLastEvent, p(10, 10));
+ moveAtLeastTouchSlop(mLastEvent);
send(mLastEvent);
// Wait for the finger lifting from the second view.
@@ -402,7 +443,6 @@ public class TouchExplorerTest {
// Manually construct the next move event. Using moveEachPointers() will batch the move
// event onto the pointer up event which will mean that the move event still has a pointer
// count of 3.
- // Todo: refactor to avoid using batching as there is no special reason to do it that way.
float[] x = new float[2];
float[] y = new float[2];
x[0] = mLastEvent.getX(0) + 100;
@@ -734,6 +774,9 @@ public class TouchExplorerTest {
}
}
+ private void moveAtLeastTouchSlop(MotionEvent event) {
+ moveEachPointers(event, p(2 * mTouchSlop, 0));
+ }
/**
* A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
* invoked.
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 988cd818ca28..feb6bd930bf3 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -34,6 +36,7 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -49,8 +52,9 @@ public final class BatteryStatsServiceTest {
final Context context = InstrumentationRegistry.getContext();
mBgThread = new HandlerThread("bg thread");
mBgThread.start();
- mBatteryStatsService = new BatteryStatsService(context,
- context.getCacheDir(), new Handler(mBgThread.getLooper()));
+ File systemDir = context.getCacheDir();
+ Handler handler = new Handler(mBgThread.getLooper());
+ mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
}
@After
@@ -121,4 +125,14 @@ public final class BatteryStatsServiceTest {
waitThread.join(1000);
}
}
+
+ @Test
+ public void testSavingStatsdAtomPullTimestamp() {
+ mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
+ assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+ .isEqualTo(1234);
+ mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
+ assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+ .isEqualTo(5478);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 9e5a0479ea70..3a3dd6ea2746 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -59,6 +60,7 @@ import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.face.UsageStats;
import org.junit.Before;
@@ -103,7 +105,7 @@ public class FaceAuthenticationClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -112,6 +114,8 @@ public class FaceAuthenticationClientTest {
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private BiometricManager mBiometricManager;
+ @Mock
+ private LockoutTracker mLockoutTracker;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -236,27 +240,63 @@ public class FaceAuthenticationClientTest {
verify(mCallback).onClientFinished(client, true);
}
+ @Test
+ public void authWithNoLockout() throws RemoteException {
+ when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+ LockoutTracker.LOCKOUT_NONE);
+
+ final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+ client.start(mCallback);
+
+ verify(mHal).authenticate(OP_ID);
+ }
+
+ @Test
+ public void authWithLockout() throws RemoteException {
+ when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn(
+ LockoutTracker.LOCKOUT_PERMANENT);
+
+ final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker);
+ client.start(mCallback);
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt());
+ verify(mHal, never()).authenticate(anyInt());
+ }
+
private FaceAuthenticationClient createClient() throws RemoteException {
return createClient(2 /* version */, mClientMonitorCallbackConverter,
- false /* allowBackgroundAuthentication */);
+ false /* allowBackgroundAuthentication */,
+ null /* lockoutTracker */);
}
private FaceAuthenticationClient createClientWithNullListener() throws RemoteException {
return createClient(2 /* version */, null /* listener */,
- true /* allowBackgroundAuthentication */);
+ true /* allowBackgroundAuthentication */,
+ null /* lockoutTracker */);
}
private FaceAuthenticationClient createClient(int version) throws RemoteException {
return createClient(version, mClientMonitorCallbackConverter,
- false /* allowBackgroundAuthentication */);
+ false /* allowBackgroundAuthentication */,
+ null /* lockoutTracker */);
+ }
+
+ private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker)
+ throws RemoteException {
+ return createClient(0 /* version */,
+ mClientMonitorCallbackConverter,
+ true /* allowBackgroundAuthentication */,
+ lockoutTracker);
}
private FaceAuthenticationClient createClient(int version,
ClientMonitorCallbackConverter listener,
- boolean allowBackgroundAuthentication) throws RemoteException {
+ boolean allowBackgroundAuthentication,
+ LockoutTracker lockoutTracker) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
.setOpPackageName("test-owner")
.setUserId(USER_ID)
@@ -270,7 +310,7 @@ public class FaceAuthenticationClientTest {
false /* restricted */, options, 4 /* cookie */,
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
- mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication,
+ mUsageStats, lockoutTracker, allowBackgroundAuthentication,
null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index ade3e8275157..fbf0e13c2ac9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -87,7 +87,7 @@ public class FaceDetectClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -170,7 +170,7 @@ public class FaceDetectClientTest {
private FaceDetectClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FaceDetectClient(mContext, () -> aidl, mToken,
99 /* requestId */, mClientMonitorCallbackConverter,
new FaceAuthenticateOptions.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 54d116f07805..128f3149e6d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -83,7 +83,7 @@ public class FaceEnrollClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -150,7 +150,7 @@ public class FaceEnrollClientTest {
private FaceEnrollClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,
USER_ID, HAT, "com.foo.bar", 44 /* requestId */,
mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
new file mode 100644
index 000000000000..c8bfaa90d863
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceGenerateChallengeClientTest {
+ private static final String TAG = "FaceGenerateChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long CHALLENGE = 200;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FaceGenerateChallengeClient mClient;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+ doAnswer(invocation -> {
+ mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ return null;
+ }).when(mSession).generateChallenge();
+ }
+
+ @Test
+ public void generateChallenge() throws RemoteException {
+ createClient(mListener);
+ mClient.start(mCallback);
+
+ verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void generateChallenge_nullListener() {
+ createClient(null);
+ mClient.start(mCallback);
+
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+
+ private void createClient(ClientMonitorCallbackConverter listener) {
+ mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener,
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
new file mode 100644
index 000000000000..9d0c84edb366
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceGetFeatureClientTest {
+ private static final String TAG = "FaceGetFeatureClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+ private FaceGetFeatureClient mClient;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener,
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature);
+
+ when(mAidlSession.getSession()).thenReturn(mSession);
+ doAnswer(invocation -> {
+ mClient.onFeatureGet(true, new byte[]{
+ AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)});
+ return null;
+ }).when(mSession).getFeatures();
+ }
+
+ @Test
+ public void getFeature() throws RemoteException {
+ ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class);
+ ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class);
+ mClient.start(mCallback);
+
+ verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(),
+ featureState.capture());
+ assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature));
+ assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
new file mode 100644
index 000000000000..1b4c01723027
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Presubmit
+@SmallTest
+public class FaceInternalCleanupClientTest {
+ private static final String TAG = "FaceInternalCleanupClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Face> mBiometricUtils;
+ @Mock
+ private Map<Integer, Long> mAuthenticatorIds;
+
+ private final List<Face> mEnrolledList = new ArrayList<>();
+ private final int mBiometricId = 1;
+ private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+ private FaceInternalCleanupClient mClient;
+ private List<Integer> mAddedIds;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mEnrolledList.add(mFace);
+ mAddedIds = new ArrayList<>();
+ mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG,
+ SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils,
+ mAuthenticatorIds) {
+ @Override
+ protected void onAddUnknownTemplate(int userId,
+ @NonNull BiometricAuthenticator.Identifier identifier) {
+ mAddedIds.add(identifier.getBiometricId());
+ }
+ };
+ }
+
+ @Test
+ public void removesUnknownTemplate() throws Exception {
+ final List<Face> templates = List.of(
+ new Face("one", 1, 1),
+ new Face("two", 2, 1)
+ );
+ mClient.start(mCallback);
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+ }
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
+ }
+
+ assertThat(mAddedIds).isEmpty();
+ final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
+
+ verify(mSession, times(2)).removeEnrollments(captor.capture());
+ assertThat(captor.getAllValues().stream()
+ .flatMap(x -> Arrays.stream(x).boxed())
+ .collect(Collectors.toList()))
+ .containsExactly(1, 2);
+ verify(mCallback).onClientFinished(eq(mClient), eq(true));
+ }
+
+ @Test
+ public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception {
+ mClient.setFavorHalEnrollments();
+ final List<Face> templates = List.of(
+ new Face("one", 1, 1),
+ new Face("two", 2, 1)
+ );
+ mClient.start(mCallback);
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+ }
+
+ assertThat(mAddedIds).containsExactly(1, 2);
+ verify(mSession, never()).removeEnrollments(any());
+ verify(mCallback).onClientFinished(eq(mClient), eq(true));
+ }
+
+ @Test
+ public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+ final List<Face> templates = List.of(
+ new Face("one", 1, 1),
+ new Face("two", 2, 1),
+ new Face("three", 3, 1)
+ );
+ mClient.start(mCallback);
+ for (int i = templates.size() - 1; i >= 0; i--) {
+ mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+ }
+
+ // The first template is removed after enumeration
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
+
+ // Simulate finishing the removal of the first template.
+ // |remaining| is 0 because one FaceRemovalClient is associated with only one
+ // biometrics ID.
+ mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0);
+
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+
+ // Simulate finishing the removal of the second template.
+ mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0);
+
+ assertThat(mClient.getUnknownHALTemplates()).isEmpty();
+ }
+
+ @Test
+ public void noUnknownTemplates() throws RemoteException {
+ mClient.start(mCallback);
+ mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mSession, never()).removeEnrollments(any());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
new file mode 100644
index 000000000000..8d74fd1d56be
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.face.Face;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceInternalEnumerateClientTest {
+ private static final String TAG = "FaceInternalEnumerateClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Face> mBiometricUtils;
+
+ private final int mBiometricId = 1;
+ private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */);
+ private FaceInternalEnumerateClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ final List<Face> enrolled = new ArrayList<>();
+ enrolled.add(mFace);
+ mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID,
+ TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext);
+ }
+
+ @Test
+ public void internalCleanupClient_noTemplatesRemaining() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(mFace, 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(null, 1);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback, never()).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(null, 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_templatesRemaining() throws RemoteException {
+ final Face identifier = new Face("face", 2, 1);
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(identifier, 1);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback, never()).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalCleanupClient_differentIdentifier_noTemplatesRemaining()
+ throws RemoteException {
+ final Face identifier = new Face("face", 2, 1);
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(identifier, 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1);
+ verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
index 76a5accc5fe9..1d9e9334f043 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java
@@ -76,7 +76,7 @@ public class FaceRemovalClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private BiometricUtils<Face> mUtils;
@Mock
@@ -115,7 +115,7 @@ public class FaceRemovalClientTest {
private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FaceRemovalClient(mContext, () -> aidl, mToken,
mClientMonitorCallbackConverter, biometricIds, USER_ID,
"own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
new file mode 100644
index 000000000000..dbbd69bdd3b5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutCache;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceResetLockoutClientTest {
+ private static final String TAG = "FaceResetLockoutClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ private final byte[] mHardwareAuthToken = new byte[69];
+ @Mock
+ private LockoutCache mLockoutTracker;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+
+ private FaceResetLockoutClient mClient;
+
+ @Before
+ public void setUp() {
+ mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID,
+ mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker,
+ mLockoutResetDispatcher, BIOMETRIC_STRONG);
+
+ when(mAidlSession.getSession()).thenReturn(mSession);
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ }
+
+ @Test
+ public void testResetLockout_onLockoutCleared() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onLockoutCleared();
+ return null;
+ }).when(mSession).resetLockout(any());
+ mClient.start(mCallback);
+
+ verify(mSession).resetLockout(any());
+ verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1);
+ verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void testResetLockout_onError() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onError(0, 0);
+ return null;
+ }).when(mSession).resetLockout(any());
+ mClient.start(mCallback);
+
+ verify(mSession).resetLockout(any());
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID,
+ BIOMETRIC_STRONG, -1);
+ verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID,
+ LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
new file mode 100644
index 000000000000..fb5502a1795d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceRevokeChallengeClientTest {
+ private static final String TAG = "FaceRevokeChallengeClientTest";
+ private static final long CHALLENGE = 200L;
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FaceRevokeChallengeClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG,
+ SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+ }
+
+ @Test
+ public void revokeChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE);
+ return null;
+ }).when(mSession).revokeChallenge(CHALLENGE);
+ mClient.start(mCallback);
+
+ verify(mSession).revokeChallenge(CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
new file mode 100644
index 000000000000..eb8cc9c63e75
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.aidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FaceSetFeatureClientTest {
+ private static final String TAG = "FaceSetFeatureClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION;
+ private final boolean mEnabled = true;
+ private final byte[] mHardwareAuthToken = new byte[69];
+ private FaceSetFeatureClient mClient;
+ TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID,
+ TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled,
+ mHardwareAuthToken);
+ }
+
+ @Test
+ public void setFeature_onFeatureSet() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onFeatureSet(true);
+ return null;
+ }).when(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled));
+ mClient.start(mCallback);
+
+ verify(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+ eq(mEnabled));
+ verify(mListener).onFeatureSet(true, mFeature);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void setFeature_onError() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onError(0, 0);
+ return null;
+ }).when(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+ eq(mEnabled));
+ mClient.start(mCallback);
+
+ verify(mSession).setFeature(any(),
+ eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)),
+ eq(mEnabled));
+ verify(mListener).onFeatureSet(false, mFeature);
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index be9f52e00b16..7a293e80c183 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -74,7 +74,7 @@ public class SensorTest {
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
- private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+ private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
@@ -94,7 +94,7 @@ public class SensorTest {
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
- private Sensor.HalSessionCallback mHalCallback;
+ private AidlResponseHandler mHalCallback;
@Before
public void setUp() {
@@ -111,10 +111,9 @@ public class SensorTest {
mBiometricService,
() -> USER_ID,
mUserSwitchCallback);
- mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
- TAG, mScheduler, SENSOR_ID,
- USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHalSessionCallback);
+ mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+ mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ mHardwareUnavailableCallback);
}
@Test
@@ -153,11 +152,11 @@ public class SensorTest {
sensorProps.commonProps.sensorId = 1;
final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
- sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
sensorProps.sensorType, sensorProps.supportsDetectInteraction,
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
- internalProp, mLockoutResetDispatcher, mBiometricContext);
+ final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
+ null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
mScheduler.reset();
@@ -181,7 +180,7 @@ public class SensorTest {
sensorProps.commonProps.sensorId = 1;
final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
- sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,
sensorProps.sensorType, sensorProps.supportsDetectInteraction,
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
new file mode 100644
index 000000000000..9a40e8a7201a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.face.EnrollmentType;
+import android.hardware.biometrics.face.Feature;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalBool;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricsFace mSession;
+ @Mock
+ FaceManager mFaceManager;
+ @Mock
+ private AidlResponseHandler mAidlResponseHandler;
+ @Mock
+ private HardwareAuthToken mHardwareAuthToken;
+ @Mock
+ private Clock mClock;
+
+ private final long mChallenge = 100L;
+ private AidlToHidlAdapter mAidlToHidlAdapter;
+ private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
+ private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
+ private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
+
+ @Before
+ public void setUp() throws RemoteException {
+ TestableContext testableContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+ testableContext.addMockSystemService(FaceManager.class, mFaceManager);
+ mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
+ mAidlResponseHandler, mClock);
+ mHardwareAuthToken.timestamp = new Timestamp();
+ mHardwareAuthToken.mac = new byte[10];
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+ result.value = mChallenge;
+
+ when(mSession.generateChallenge(anyInt())).thenReturn(result);
+ when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
+ }
+
+ @Test
+ public void testGenerateChallengeCache() throws RemoteException {
+ verify(mSession).setCallback(any());
+
+ final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
+
+ mAidlToHidlAdapter.generateChallenge();
+
+ verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+ verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
+ assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
+
+ forwardTime(10 /* seconds */);
+ mAidlToHidlAdapter.generateChallenge();
+ forwardTime(20 /* seconds */);
+ mAidlToHidlAdapter.generateChallenge();
+
+ //Confirms that the challenge is cached and the hal method is not called again
+ verifyNoMoreInteractions(mSession);
+ verify(mAidlResponseHandler, times(3))
+ .onChallengeGenerated(mChallenge);
+
+ forwardTime(60 /* seconds */);
+ mAidlToHidlAdapter.generateChallenge();
+
+ //HAL method called after challenge has timed out
+ verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
+ }
+
+ @Test
+ public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
+ for (int i = 0; i < 3; i++) {
+ mAidlToHidlAdapter.generateChallenge();
+ forwardTime(10 /* seconds */);
+ }
+ for (int i = 0; i < 3; i++) {
+ mAidlToHidlAdapter.revokeChallenge(0);
+ forwardTime((i + 1) * 10 /* seconds */);
+ }
+
+ verify(mSession).revokeChallenge();
+ }
+
+ @Test
+ public void testRevokeChallenge_timeout() throws RemoteException {
+ mAidlToHidlAdapter.generateChallenge();
+ mAidlToHidlAdapter.generateChallenge();
+ forwardTime(700);
+ mAidlToHidlAdapter.generateChallenge();
+ mAidlToHidlAdapter.revokeChallenge(0);
+
+ verify(mSession).revokeChallenge();
+ }
+
+ @Test
+ public void testEnroll() throws RemoteException {
+ ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
+ EnrollmentType.DEFAULT, mFeatures,
+ null /* previewSurface */);
+ ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+ verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
+
+ ArrayList<Integer> features = featureCaptor.getValue();
+
+ assertThat(features).containsExactly(
+ AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0]));
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testAuthenticate() throws RemoteException {
+ final int operationId = 2;
+ ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+ verify(mSession).authenticate(operationId);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testDetectInteraction() throws RemoteException {
+ ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+ verify(mSession).authenticate(0);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testEnumerateEnrollments() throws RemoteException {
+ mAidlToHidlAdapter.enumerateEnrollments();
+
+ verify(mSession).enumerate();
+ }
+
+ @Test
+ public void testRemoveEnrollment() throws RemoteException {
+ final int[] enrollments = new int[]{1};
+ mAidlToHidlAdapter.removeEnrollments(enrollments);
+
+ verify(mSession).remove(enrollments[0]);
+ }
+
+ @Test
+ public void testGetFeatures_onResultSuccess() throws RemoteException {
+ final OptionalBool result = new OptionalBool();
+ result.status = Status.OK;
+ result.value = true;
+ ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+ when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+ mAidlToHidlAdapter.setFeature(mFeature);
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+ assertThat(featureRetrieved.getValue()[0]).isEqualTo(
+ AidlConversionUtils.convertFrameworkToAidlFeature(mFeature));
+ }
+
+ @Test
+ public void testGetFeatures_onResultFailed() throws RemoteException {
+ final OptionalBool result = new OptionalBool();
+ result.status = Status.OK;
+ result.value = false;
+ ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class);
+
+ when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+ mAidlToHidlAdapter.setFeature(mFeature);
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
+ assertThat(featureRetrieved.getValue().length).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetFeatures_onStatusFailed() throws RemoteException {
+ final OptionalBool result = new OptionalBool();
+ result.status = Status.INTERNAL_ERROR;
+ result.value = false;
+
+ when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
+
+ mAidlToHidlAdapter.setFeature(mFeature);
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+ verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0);
+ }
+
+ @Test
+ public void testGetFeatures_featureNotSet() throws RemoteException {
+ mAidlToHidlAdapter.getFeatures();
+
+ verify(mSession, never()).getFeature(eq(mFeature), anyInt());
+ verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
+ }
+
+ @Test
+ public void testSetFeatureSuccessful() throws RemoteException {
+ byte feature = Feature.REQUIRE_ATTENTION;
+ boolean enabled = true;
+
+ when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
+
+ mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+ verify(mAidlResponseHandler).onFeatureSet(feature);
+ }
+
+ @Test
+ public void testSetFeatureFailed() throws RemoteException {
+ byte feature = Feature.REQUIRE_ATTENTION;
+ boolean enabled = true;
+
+ when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
+ .thenReturn(Status.INTERNAL_ERROR);
+
+ mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+
+ verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
+ 0 /* vendorCode */);
+ }
+
+ @Test
+ public void testGetAuthenticatorId() throws RemoteException {
+ final long authenticatorId = 2L;
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+ result.value = authenticatorId;
+
+ when(mSession.getAuthenticatorId()).thenReturn(result);
+
+ mAidlToHidlAdapter.getAuthenticatorId();
+
+ verify(mSession).getAuthenticatorId();
+ verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
+ }
+
+ @Test
+ public void testResetLockout() throws RemoteException {
+ mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+ ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
+
+ verify(mSession).resetLockout(hatCaptor.capture());
+
+ assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken));
+ }
+
+ private ArrayList<Byte> processHAT(HardwareAuthToken hat) {
+ ArrayList<Byte> hardwareAuthToken = new ArrayList<>();
+ for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) {
+ hardwareAuthToken.add(b);
+ }
+ return hardwareAuthToken;
+ }
+
+ private void forwardTime(long seconds) {
+ when(mClock.millis()).thenReturn(seconds * 1000);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 8a11e31014d5..79a528c59f49 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -53,11 +53,15 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -66,6 +70,7 @@ import com.android.server.biometrics.log.Probe;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
import org.junit.Before;
import org.junit.Rule;
@@ -102,6 +107,9 @@ public class FingerprintAuthenticationClientTest {
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private ISession mHal;
@@ -124,7 +132,7 @@ public class FingerprintAuthenticationClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
@@ -135,6 +143,8 @@ public class FingerprintAuthenticationClientTest {
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private Clock mClock;
+ @Mock
+ private LockoutTracker mLockoutTracker;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -425,37 +435,65 @@ public class FingerprintAuthenticationClientTest {
verify(mCallback).onClientFinished(client, true);
}
+ @Test
+ public void testLockoutTracker_authSuccess() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1 /* version */,
+ true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+ mLockoutTracker);
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+ verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+ verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testLockoutTracker_authFailed() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient(1 /* version */,
+ true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
+ mLockoutTracker);
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+ verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt());
+ verify(mLockoutTracker).addFailedAttemptForUser(USER_ID);
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
- mClientMonitorCallbackConverter);
+ mClientMonitorCallbackConverter, null);
}
private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()
throws RemoteException {
return createClient(100 /* version */, false /* allowBackgroundAuthentication */,
- mClientMonitorCallbackConverter);
+ mClientMonitorCallbackConverter, null);
}
private FingerprintAuthenticationClient createClient(int version) throws RemoteException {
return createClient(version, true /* allowBackgroundAuthentication */,
- mClientMonitorCallbackConverter);
+ mClientMonitorCallbackConverter, null);
}
private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */,
- null /* listener */);
+ null, /* listener */null);
}
private FingerprintAuthenticationClient createClient(int version,
- boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener)
+ boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener,
+ LockoutTracker lockoutTracker)
throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()
.setOpPackageName("test-owner")
- .setUserId(5)
- .setSensorId(9)
+ .setUserId(USER_ID)
+ .setSensorId(SENSOR_ID)
.build();
return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,
REQUEST_ID, listener, OP_ID,
@@ -463,10 +501,11 @@ public class FingerprintAuthenticationClientTest {
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
- null /* taskStackListener */, null /* lockoutCache */,
+ null /* taskStackListener */,
mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
mSensorProps,
- new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
+ new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+ lockoutTracker) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 78d3a9dd9f9e..a467c84845cd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -83,7 +83,7 @@ public class FingerprintDetectClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Captor
private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;
@Captor
@@ -150,7 +150,7 @@ public class FingerprintDetectClientTest {
@Test
public void testWhenListenerIsNull() {
- final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext, () -> aidl,
mToken, 6 /* requestId */, null /* listener */,
new FingerprintAuthenticateOptions.Builder()
@@ -173,7 +173,7 @@ public class FingerprintDetectClientTest {
private FingerprintDetectClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FingerprintDetectClient(mContext, () -> aidl, mToken,
6 /* requestId */, mClientMonitorCallbackConverter,
new FingerprintAuthenticateOptions.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index ef253801b118..c7eb1db3e74b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -102,7 +102,7 @@ public class FingerprintEnrollClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
- private Sensor.HalSessionCallback mHalSessionCallback;
+ private AidlResponseHandler mAidlResponseHandler;
@Mock
private Probe mLuxProbe;
@Captor
@@ -291,7 +291,7 @@ public class FingerprintEnrollClientTest {
private FingerprintEnrollClient createClient(int version) throws RemoteException {
when(mHal.getInterfaceVersion()).thenReturn(version);
- final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
+ final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);
return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID,
mClientMonitorCallbackConverter, 0 /* userId */,
HAT, "owner", mBiometricUtils, 8 /* sensorId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
new file mode 100644
index 000000000000..840961938cb2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintGenerateChallengeClientTest {
+ private static final String TAG = "FingerprintGenerateChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long CHALLENGE = 200;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FingerprintGenerateChallengeClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken,
+ mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext);
+ }
+
+ @Test
+ public void generateChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ return null;
+ }).when(mSession).generateChallenge();
+ mClient.start(mCallback);
+
+ verify(mSession).generateChallenge();
+ verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index 580644347c84..c9482ceb00f5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.Fingerprint;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -41,7 +42,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -70,9 +70,9 @@ public class FingerprintInternalCleanupClientTest {
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Mock
- private AidlSession mAidlSession;
+ ISession mSession;
@Mock
- private ISession mSession;
+ private AidlSession mAidlSession;
@Mock
private BiometricLogger mLogger;
@Mock
@@ -87,15 +87,16 @@ public class FingerprintInternalCleanupClientTest {
@Before
public void setup() {
- when(mAidlSession.getSession()).thenReturn(mSession);
mAddedIds = new ArrayList<>();
+
+ when(mAidlSession.getSession()).thenReturn(mSession);
}
- @Ignore("TODO(b/229015801): verify cleanup behavior")
@Test
public void removesUnknownTemplate() throws Exception {
mClient = createClient();
+ final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
final List<Fingerprint> templates = List.of(
new Fingerprint("one", 1, 1),
new Fingerprint("two", 2, 1)
@@ -108,8 +109,8 @@ public class FingerprintInternalCleanupClientTest {
mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);
}
+ verify(mSession).enumerateEnrollments();
assertThat(mAddedIds).isEmpty();
- final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
verify(mSession, times(2)).removeEnrollments(captor.capture());
assertThat(captor.getAllValues().stream()
.flatMap(x -> Arrays.stream(x).boxed())
@@ -132,13 +133,15 @@ public class FingerprintInternalCleanupClientTest {
mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
}
+ verify(mSession).enumerateEnrollments();
assertThat(mAddedIds).containsExactly(1, 2);
verify(mSession, never()).removeEnrollments(any());
verify(mCallback).onClientFinished(eq(mClient), eq(true));
}
@Test
- public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() {
+ public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled()
+ throws RemoteException {
mClient = createClient();
final List<Fingerprint> templates = List.of(
@@ -150,6 +153,8 @@ public class FingerprintInternalCleanupClientTest {
for (int i = templates.size() - 1; i >= 0; i--) {
mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
}
+
+ verify(mSession).enumerateEnrollments();
// The first template is removed after enumeration
assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
new file mode 100644
index 000000000000..723f916f99c8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@Presubmit
+@SmallTest
+public class FingerprintInternalEnumerateClientTest {
+ private static final String TAG = "FingerprintInternalEnumerateClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+
+ private FingerprintInternalEnumerateClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ List<Fingerprint> enrolled = new ArrayList<>();
+ enrolled.add(new Fingerprint("one", 1, 1));
+ mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken,
+ USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger,
+ mBiometricContext);
+ }
+
+ @Test
+ public void internalEnumerate_unknownTemplates() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1);
+ mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().stream()
+ .flatMap(x -> Stream.of(x.getBiometricId()))
+ .collect(Collectors.toList())).containsExactly(2, 3);
+ verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void internalEnumerate_noUnknownTemplates() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0);
+ return null;
+ }).when(mSession).enumerateEnrollments();
+ mClient.start(mCallback);
+
+ verify(mSession).enumerateEnrollments();
+ assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0);
+ verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
new file mode 100644
index 000000000000..64f07e20e958
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.Fingerprint;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Presubmit
+@SmallTest
+public class FingerprintRemovalClientTest {
+ private static final String TAG = "FingerprintRemovalClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallbackConverter mListener;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private Map<Integer, Long> mAuthenticatorIds;
+
+ private FingerprintRemovalClient mClient;
+ private int[] mBiometricIds = new int[]{1, 2};
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener,
+ mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID,
+ mBiometricLogger, mBiometricContext, mAuthenticatorIds);
+ }
+
+ @Test
+ public void removalMultipleFingerprints() throws RemoteException {
+ when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(
+ List.of(new Fingerprint("three", 3, 1)));
+ doAnswer(invocation -> {
+ mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+ mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+ return null;
+ }).when(mSession).removeEnrollments(mBiometricIds);
+ mClient.start(mCallback);
+
+ verify(mSession).removeEnrollments(mBiometricIds);
+ verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+ eq(USER_ID), anyInt());
+ verifyNoMoreInteractions(mAuthenticatorIds);
+ verify(mListener, times(2)).onRemoved(any(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void removeFingerprint_nullIdentifier() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onRemoved(null, 0);
+ return null;
+ }).when(mSession).removeEnrollments(mBiometricIds);
+ mClient.start(mCallback);
+
+ verify(mSession).removeEnrollments(mBiometricIds);
+ verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt());
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+
+ @Test
+ public void removeFingerprints_noFingerprintEnrolled() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onRemoved(new Fingerprint("one", 1, 1), 1);
+ mClient.onRemoved(new Fingerprint("two", 2, 1), 0);
+ return null;
+ }).when(mSession).removeEnrollments(mBiometricIds);
+ when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>());
+
+ mClient.start(mCallback);
+
+ verify(mSession).removeEnrollments(mBiometricIds);
+ verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext),
+ eq(USER_ID), anyInt());
+ verify(mAuthenticatorIds).put(USER_ID, 0L);
+ verify(mListener, times(2)).onRemoved(any(), anyInt());
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
new file mode 100644
index 000000000000..a4746dea7ac5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintResetLockoutClientTest {
+ private static final String TAG = "FingerprintResetLockoutClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private LockoutTracker mLockoutTracker;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcher;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+
+ private FingerprintResetLockoutClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG,
+ SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69],
+ mLockoutTracker, mLockoutResetDispatcher,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void resetLockout_onLockoutCleared() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onLockoutCleared();
+ return null;
+ }).when(mSession).resetLockout(any());
+ mClient.start(mCallback);
+
+ verify(mSession).resetLockout(any());
+ verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID);
+ verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID),
+ eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
new file mode 100644
index 000000000000..f19b2f7da8a5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.aidl;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FingerprintRevokeChallengeClientTest {
+ private static final String TAG = "FingerprintRevokeChallengeClientTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final long CHALLENGE = 200;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AidlSession mAidlSession;
+ @Mock
+ private ISession mSession;
+ @Mock
+ private IBinder mToken;
+ @Mock
+ private ClientMonitorCallback mCallback;
+ @Mock
+ private Context mContext;
+ @Mock
+ private BiometricLogger mBiometricLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+
+ private FingerprintRevokeChallengeClient mClient;
+
+ @Before
+ public void setUp() {
+ when(mAidlSession.getSession()).thenReturn(mSession);
+
+ mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken,
+ USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE);
+ }
+
+ @Test
+ public void revokeChallenge_sameChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeRevoked(CHALLENGE);
+ return null;
+ }).when(mSession).revokeChallenge(CHALLENGE);
+ mClient.start(mCallback);
+
+ verify(mSession).revokeChallenge(CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, true);
+ }
+
+ @Test
+ public void revokeChallenge_differentChallenge() throws RemoteException {
+ doAnswer(invocation -> {
+ mClient.onChallengeRevoked(CHALLENGE + 1);
+ return null;
+ }).when(mSession).revokeChallenge(CHALLENGE);
+ mClient.start(mCallback);
+
+ verify(mSession).revokeChallenge(CHALLENGE);
+ verify(mCallback).onClientFinished(mClient, false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 15d7601dde34..410260074fbd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -75,7 +75,7 @@ public class SensorTest {
@Mock
private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
@Mock
- private Sensor.HalSessionCallback.Callback mHalSessionCallback;
+ private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
@Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
@@ -97,7 +97,7 @@ public class SensorTest {
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
- private Sensor.HalSessionCallback mHalCallback;
+ private AidlResponseHandler mHalCallback;
@Before
public void setUp() {
@@ -113,10 +113,9 @@ public class SensorTest {
mBiometricService,
() -> USER_ID,
mUserSwitchCallback);
- mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()),
- TAG, mScheduler, SENSOR_ID,
- USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHalSessionCallback);
+ mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
+ mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ mHardwareUnavailableCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
new file mode 100644
index 000000000000..b78ba82bd8fe
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class AidlToHidlAdapterTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricsFingerprint mSession;
+ @Mock
+ private AidlResponseHandler mAidlResponseHandler;
+ @Mock
+ private HardwareAuthToken mHardwareAuthToken;
+
+ private final long mChallenge = 100L;
+ private final int mUserId = 0;
+ private AidlToHidlAdapter mAidlToHidlAdapter;
+
+ @Before
+ public void setUp() {
+ mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+ mAidlResponseHandler);
+ mHardwareAuthToken.timestamp = new Timestamp();
+ mHardwareAuthToken.mac = new byte[10];
+ }
+
+ @Test
+ public void testGenerateChallenge() throws RemoteException {
+ when(mSession.preEnroll()).thenReturn(mChallenge);
+ mAidlToHidlAdapter.generateChallenge();
+
+ verify(mSession).preEnroll();
+ verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
+ }
+
+ @Test
+ public void testRevokeChallenge() throws RemoteException {
+ mAidlToHidlAdapter.revokeChallenge(mChallenge);
+
+ verify(mSession).postEnroll();
+ verify(mAidlResponseHandler).onChallengeRevoked(0L);
+ }
+
+ @Test
+ public void testEnroll() throws RemoteException {
+ final ICancellationSignal cancellationSignal =
+ mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+
+ verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testAuthenticate() throws RemoteException {
+ final int operationId = 2;
+ final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+
+ verify(mSession).authenticate(operationId, mUserId);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testDetectInteraction() throws RemoteException {
+ final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+
+ verify(mSession).authenticate(0 /* operationId */, mUserId);
+
+ cancellationSignal.cancel();
+
+ verify(mSession).cancel();
+ }
+
+ @Test
+ public void testEnumerateEnrollments() throws RemoteException {
+ mAidlToHidlAdapter.enumerateEnrollments();
+
+ verify(mSession).enumerate();
+ }
+
+ @Test
+ public void testRemoveEnrollment() throws RemoteException {
+ final int[] enrollmentIds = new int[]{1};
+ mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+ verify(mSession).remove(mUserId, enrollmentIds[0]);
+ }
+
+ @Test
+ public void testRemoveMultipleEnrollments() throws RemoteException {
+ final int[] enrollmentIds = new int[]{1, 2};
+ mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+
+ verify(mSession).remove(mUserId, 0);
+ }
+
+ @Test
+ public void testResetLockout() throws RemoteException {
+ mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+
+ verify(mAidlResponseHandler).onLockoutCleared();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 67b70684eede..12d6161eb718 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.contentcapture;
+import static android.view.contentprotection.flags.Flags.FLAG_PARSE_GROUPS_CONFIG_ENABLED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,6 +35,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.contentcapture.ContentCaptureServiceInfo;
import android.view.contentcapture.ContentCaptureEvent;
@@ -56,6 +59,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.List;
+
/**
* Test for {@link ContentCaptureManagerService}.
*
@@ -84,6 +89,8 @@ public class ContentCaptureManagerServiceTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock private UserManagerInternal mMockUserManagerInternal;
@Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
@@ -437,23 +444,81 @@ public class ContentCaptureManagerServiceTest {
}
@Test
- public void parseContentProtectionGroupsConfig_null() {
+ public void parseContentProtectionGroupsConfig_disabled_null() {
+ mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
}
@Test
- public void parseContentProtectionGroupsConfig_empty() {
+ public void parseContentProtectionGroupsConfig_disabled_empty() {
+ mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
}
@Test
- public void parseContentProtectionGroupsConfig_notEmpty() {
+ public void parseContentProtectionGroupsConfig_disabled_notEmpty() {
+ mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty();
}
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_null() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_empty() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_singleValue() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a"))
+ .isEqualTo(List.of(List.of("a")));
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_multipleValues() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a,b"))
+ .isEqualTo(List.of(List.of("a", "b")));
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_multipleGroups() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e"))
+ .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e")));
+ }
+
+ @Test
+ public void parseContentProtectionGroupsConfig_enabled_emptyValues() {
+ mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);
+ ContentCaptureManagerService service = new ContentCaptureManagerService(sContext);
+
+ assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;"))
+ .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e")));
+ }
+
private class TestContentCaptureManagerService extends ContentCaptureManagerService {
TestContentCaptureManagerService() {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 9b32a809d2b5..de3cfbf859ff 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -54,8 +54,8 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 16fdfb16444a..76aa40c04e3d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -77,7 +77,7 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.AlarmManagerInternal;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index a029db922372..fa3c7a4c4769 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock;
import android.content.Context;
-import com.android.server.PersistentDataBlockManagerInternal;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 23f14f8468bb..02b86db6ab6f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -53,8 +53,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.android.server.pdb.PersistentDataBlockManagerInternal;
import org.junit.After;
import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
index 75d71daa208d..06f117bdbd65 100644
--- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
@@ -38,9 +38,10 @@ import org.junit.runners.JUnit4;
public class BluetoothRouteControllerTest {
private final BluetoothRouteController.BluetoothRoutesUpdatedListener
- mBluetoothRoutesUpdatedListener = routes -> {
- // Empty on purpose.
- };
+ mBluetoothRoutesUpdatedListener =
+ () -> {
+ // Empty on purpose.
+ };
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index ec4b8a804533..14b121d3945c 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -38,7 +38,7 @@ import org.junit.runners.JUnit4;
public class DeviceRouteControllerTest {
private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener =
- deviceRoute -> {
+ () -> {
// Empty on purpose.
};
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index d85768dd7588..f94aff706a67 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,6 +26,7 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +67,7 @@ import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
@@ -128,6 +130,14 @@ public class MediaProjectionManagerServiceTest {
}
};
+ private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
+ new MediaProjectionManagerService.Injector() {
+ @Override
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+ return mMediaProjectionMetricsLogger;
+ }
+ };
+
private Context mContext;
private MediaProjectionManagerService mService;
private OffsettableClock mClock;
@@ -142,6 +152,8 @@ public class MediaProjectionManagerServiceTest {
private PackageManager mPackageManager;
@Mock
private IMediaProjectionWatcherCallback mWatcherCallback;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
@Captor
private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
@@ -734,6 +746,25 @@ public class MediaProjectionManagerServiceTest {
}
@Test
+ public void setContentRecordingSession_success_logsCaptureInProgress()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+ projection.uid,
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+ );
+ }
+
+ @Test
public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
throws Exception {
mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
diff --git a/services/tests/servicestests/src/com/android/server/pdb/OWNERS b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
new file mode 100644
index 000000000000..6dfb888dedad
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pdb/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pdb/OWNERS
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index ca5cfa5b60f5..95441060f0e5 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -27,6 +27,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index bc826a3cf4a6..04158c4d4f93 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -31,6 +31,8 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
@@ -49,6 +51,8 @@ import java.io.File;
import java.io.FileOutputStream;
public class HapticFeedbackCustomizationTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Rule public MockitoRule rule = MockitoJUnit.rule();
// Pairs of valid vibration XML along with their equivalent VibrationEffect.
@@ -77,6 +81,7 @@ public class HapticFeedbackCustomizationTest {
@Before
public void setUp() {
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
}
@Test
@@ -87,6 +92,21 @@ public class HapticFeedbackCustomizationTest {
}
@Test
+ public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+ // Valid customization XML.
+ String xml = "<haptic-feedback-constants>"
+ + "<constant id=\"10\">"
+ + COMPOSITION_VIBRATION_XML
+ + "</constant>"
+ + "</haptic-feedback-constants>";
+ setupCustomizationFile(xml);
+
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+ .isNull();
+ }
+
+ @Test
public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 233a2076a867..84d42d42f834 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -18,16 +18,20 @@ package com.android.server.wm;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.utils.LastCallVerifier.lastCall;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import android.graphics.Rect;
@@ -35,8 +39,9 @@ import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -118,102 +123,168 @@ public class DimmerTests extends WindowTestsBase {
}
}
- private MockSurfaceBuildingContainer mHost;
- private Dimmer mDimmer;
- private SurfaceControl.Transaction mTransaction;
- private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+ static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+ public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+ SurfaceAnimationRunner runner) {
+ return sTestAnimation;
+ }
+ }
- private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
+ private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
@Override
public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @AnimationType int type) {
+ AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
}
}
+ private MockSurfaceBuildingContainer mHost;
+ private Dimmer mDimmer;
+ private SurfaceControl.Transaction mTransaction;
+ private TestWindowContainer mChild;
+ private static AnimationAdapter sTestAnimation;
+ private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
+
@Before
public void setUp() throws Exception {
mHost = new MockSurfaceBuildingContainer(mWm);
- mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
mTransaction = spy(StubTransaction.class);
- mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
+ mChild = new TestWindowContainer(mWm);
+ if (Flags.dimmerRefactor()) {
+ sTestAnimation = spy(new MockAnimationAdapter());
+ mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
+ } else {
+ sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
+ mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
+ }
}
@Test
public void testUpdateDimsAppliesCrop() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
+ mDimmer.dimAbove(mChild, alpha);
int width = 100;
int height = 300;
- mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+ mDimmer.getDimBounds().set(0, 0, width, height);
mDimmer.updateDims(mTransaction);
- verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
- verify(mTransaction).show(getDimLayer());
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height);
+ verify(mTransaction).show(mDimmer.getDimLayer());
}
@Test
- public void testDimAboveWithChildCreatesSurfaceAboveChild() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ final float alpha = 0.8f;
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, alpha);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+
+ assertNotNull("Dimmer should have created a surface", dimLayer);
+
+ mDimmer.updateDims(mTransaction);
+ verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1);
+ verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+ }
+ @Test
+ public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, alpha);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1);
+ verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1);
}
@Test
- public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ final float alpha = 0.7f;
+ mHost.addChild(mChild, 0);
+ mDimmer.dimBelow(mChild, alpha, 50);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
- final float alpha = 0.8f;
- mDimmer.dimBelow(child, alpha, 0);
- SurfaceControl dimLayer = getDimLayer();
+ assertNotNull("Dimmer should have created a surface", dimLayer);
+
+ mDimmer.updateDims(mTransaction);
+ verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1);
+ verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+ verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50);
+ }
+
+ @Test
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ final float alpha = 0.7f;
+ mHost.addChild(mChild, 0);
+ mDimmer.dimBelow(mChild, alpha, 50);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
+ verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
}
@Test
- public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+
+ final float alpha = 0.8f;
+ // Dim once
+ mDimmer.dimBelow(mChild, alpha, 0);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ // Reset, and don't dim
+ mDimmer.resetDimStates();
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction).show(dimLayer);
+ verify(mTransaction).remove(dimLayer);
+ }
+
+ @Test
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ mDimmer.dimAbove(mChild, alpha);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction);
- verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
- SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+ verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
+ any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
+ anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
verify(mHost.getPendingTransaction()).remove(dimLayer);
}
@Test
public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ // Dim once
+ mDimmer.dimBelow(mChild, alpha, 0);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ // Reset and dim again
mDimmer.resetDimStates();
- mDimmer.dimAbove(child, alpha);
-
+ mDimmer.dimAbove(mChild, alpha);
mDimmer.updateDims(mTransaction);
verify(mTransaction).show(dimLayer);
verify(mTransaction, never()).remove(dimLayer);
@@ -221,14 +292,12 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testDimUpdateWhileDimming() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
-
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- final Rect bounds = mDimmer.mDimState.mDimBounds;
+ mDimmer.dimAbove(mChild, alpha);
+ final Rect bounds = mDimmer.getDimBounds();
- SurfaceControl dimLayer = getDimLayer();
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
bounds.set(0, 0, 10, 10);
mDimmer.updateDims(mTransaction);
verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
@@ -242,41 +311,56 @@ public class DimmerTests extends WindowTestsBase {
}
@Test
- public void testRemoveDimImmediately() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testRemoveDimImmediately_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, 1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction, times(1)).show(dimLayer);
- mDimmer.dimAbove(child, 1);
- SurfaceControl dimLayer = getDimLayer();
+ reset(sTestAnimation);
+ mDimmer.dontAnimateExit();
+ mDimmer.resetDimStates();
+ mDimmer.updateDims(mTransaction);
+ verify(sTestAnimation, never()).startAnimation(
+ any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).remove(dimLayer);
+ }
+
+ @Test
+ public void testRemoveDimImmediately_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, 1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
verify(mTransaction, times(1)).show(dimLayer);
- reset(mSurfaceAnimatorStarter);
+ reset(sSurfaceAnimatorStarter);
mDimmer.dontAnimateExit();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction);
- verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
- SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+ verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
+ any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
verify(mTransaction).remove(dimLayer);
}
@Test
- public void testDimmerWithBlurUpdatesTransaction() {
+ public void testDimmerWithBlurUpdatesTransaction_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
final int blurRadius = 50;
mDimmer.dimBelow(child, 0, blurRadius);
- SurfaceControl dimLayer = getDimLayer();
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
}
-
- private SurfaceControl getDimLayer() {
- return mDimmer.mDimState.mDimLayer;
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 6c48a6961bc2..9f43a1785266 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -94,6 +94,16 @@ public class SafeActivityOptionsTest {
}
@Test
+ public void test_selectiveCloneLunchRemoteTransition() {
+ final RemoteTransition transition = mock(RemoteTransition.class);
+ final SafeActivityOptions clone = new SafeActivityOptions(
+ ActivityOptions.makeRemoteTransition(transition))
+ .selectiveCloneLaunchOptions();
+
+ assertSame(clone.getOriginalOptions().getRemoteTransition(), transition);
+ }
+
+ @Test
public void test_getOptions() {
// Mock everything necessary
MockitoSession mockingSession = mockitoSession()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5205bb0038c0..7822071f3933 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -37,6 +37,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -687,4 +689,24 @@ public class TaskFragmentTest extends WindowTestsBase {
tf0.setIsolatedNav(true);
assertTrue(tf0.isIsolatedNav());
}
+
+ @Test
+ public void testGetDimBounds() {
+ final Task task = mTaskFragment.getTask();
+ final Rect taskBounds = task.getBounds();
+ mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10,
+ taskBounds.top + 10);
+ final Rect taskFragmentBounds = mTaskFragment.getBounds();
+
+ // Return Task bounds if dimming on parent Task.
+ final Rect dimBounds = new Rect();
+ mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+ mTaskFragment.getDimBounds(dimBounds);
+ assertEquals(taskBounds, dimBounds);
+
+ // Return TF bounds by default.
+ mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT);
+ mTaskFragment.getDimBounds(dimBounds);
+ assertEquals(taskFragmentBounds, dimBounds);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
new file mode 100644
index 000000000000..320d09444681
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import org.mockito.internal.verification.VerificationModeFactory;
+import org.mockito.internal.verification.api.VerificationData;
+import org.mockito.invocation.Invocation;
+import org.mockito.invocation.MatchableInvocation;
+import org.mockito.verification.VerificationMode;
+
+import java.util.List;
+
+/**
+ * Verifier to check that the last call of a method received the expected argument
+ */
+public class LastCallVerifier implements VerificationMode {
+
+ /**
+ * Allows comparing the expected invocation with the last invocation on the same method
+ */
+ public static LastCallVerifier lastCall() {
+ return new LastCallVerifier();
+ }
+
+ @Override
+ public void verify(VerificationData data) {
+ List<Invocation> invocations = data.getAllInvocations();
+ MatchableInvocation target = data.getTarget();
+ for (int i = invocations.size() - 1; i >= 0; i--) {
+ final Invocation invocation = invocations.get(i);
+ if (target.hasSameMethod(invocation)) {
+ if (target.matches(invocation)) {
+ return;
+ } else {
+ throw new LastCallMismatch(target.getInvocation(), invocation, invocations);
+ }
+ }
+ }
+ throw new RuntimeException(target + " never invoked");
+ }
+
+ @Override
+ public VerificationMode description(String description) {
+ return VerificationModeFactory.description(this, description);
+ }
+
+ static class LastCallMismatch extends RuntimeException {
+ LastCallMismatch(
+ Invocation expected, Invocation received, List<Invocation> allInvocations) {
+ super("Expected invocation " + expected + " but received " + received
+ + " as the last invocation.\nAll registered invocations:\n" + allInvocations);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
new file mode 100644
index 000000000000..1a66970a8dc2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.wm.AnimationAdapter;
+import com.android.server.wm.SurfaceAnimator;
+
+import java.io.PrintWriter;
+
+/**
+ * An empty animation adapter which just executes the finish callback
+ */
+public class MockAnimationAdapter implements AnimationAdapter {
+
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ // As the animation won't run, finish it immediately
+ finishCallback.onAnimationFinished(0, null);
+ }
+
+ @Override
+ public void onAnimationCancelled(SurfaceControl animationLeash) {}
+
+ @Override
+ public long getDurationHint() {
+ return 0;
+ }
+
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return 0;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {}
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto) {}
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f3bf026ddc6e..2e6278d9a75d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -53,6 +53,7 @@ import android.app.usage.AppStandbyInfo;
import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
+import android.app.usage.Flags;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
@@ -200,6 +201,7 @@ public class UsageStatsService extends SystemService implements
static final int MSG_ON_START = 7;
static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
+ static final int MSG_UID_REMOVED = 10;
private final Object mLock = new Object();
private Handler mHandler;
@@ -378,11 +380,10 @@ public class UsageStatsService extends SystemService implements
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
- null, mHandler);
-
+ getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL,
+ filter, null, /* Handler scheduler */ null);
getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
- new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+ new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -614,7 +615,6 @@ public class UsageStatsService extends SystemService implements
if (Intent.ACTION_USER_REMOVED.equals(action)) {
if (userId >= 0) {
mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
- mResponseStatsTracker.onUserRemoved(userId);
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >= 0) {
@@ -632,9 +632,7 @@ public class UsageStatsService extends SystemService implements
return;
}
- synchronized (mLock) {
- mResponseStatsTracker.onUidRemoved(uid);
- }
+ mHandler.obtainMessage(MSG_UID_REMOVED, uid, 0).sendToTarget();
}
}
@@ -1301,6 +1299,8 @@ public class UsageStatsService extends SystemService implements
mPendingLaunchTimeChangePackages.remove(userId);
}
mAppStandby.onUserRemoved(userId);
+ mResponseStatsTracker.onUserRemoved(userId);
+
// Cancel any scheduled jobs for this user since the user is being removed.
UsageStatsIdleService.cancelPruneJob(getContext(), userId);
UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId);
@@ -2037,6 +2037,9 @@ public class UsageStatsService extends SystemService implements
case MSG_REMOVE_USER:
onUserRemoved(msg.arg1);
break;
+ case MSG_UID_REMOVED:
+ mResponseStatsTracker.onUidRemoved(msg.arg1);
+ break;
case MSG_PACKAGE_REMOVED:
onPackageRemoved(msg.arg1, (String) msg.obj);
break;
@@ -2124,12 +2127,15 @@ public class UsageStatsService extends SystemService implements
}
private boolean canReportUsageStats() {
- if (isCallingUidSystem()) {
- return true; // System UID can always report UsageStats
- }
-
- return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
- == PackageManager.PERMISSION_GRANTED;
+ final boolean isSystem = isCallingUidSystem();
+ if (!Flags.reportUsageStatsPermission()) {
+ // If the flag is disabled, do no check for the new permission and instead return
+ // true only if the calling uid is system since System UID can always report stats.
+ return isSystem;
+ }
+ return isSystem
+ || getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
+ == PackageManager.PERMISSION_GRANTED;
}
private boolean hasObserverPermission() {
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 5d2f27dce206..35e2fcfecb6b 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -37,8 +37,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.hardware.usb.IUsbManager;
import android.hardware.usb.IDisplayPortAltModeInfoListener;
+import android.hardware.usb.IUsbManager;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.ParcelableUsbPort;
import android.hardware.usb.UsbAccessory;
@@ -46,7 +46,6 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.DisplayPortAltModeInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -1215,6 +1214,20 @@ public class UsbService extends IUsbManager.Stub {
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
"", 0);
}
+ } else if ("enable-usb-data".equals(args[0]) && args.length == 3) {
+ final String portId = args[1];
+ final boolean enable = Boolean.parseBoolean(args[2]);
+
+ if (mPortManager != null) {
+ for (UsbPort p : mPortManager.getPorts()) {
+ if (p.getId().equals(portId)) {
+ int res = p.enableUsbData(enable);
+ Slog.i(TAG, "enableUsbData " + portId + " status " + res);
+ break;
+ }
+ }
+ }
+
} else if ("ports".equals(args[0]) && args.length == 1) {
if (mPortManager != null) {
mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")),
@@ -1293,6 +1306,11 @@ public class UsbService extends IUsbManager.Stub {
pw.println("reset-displayport-status can also be used in order to set");
pw.println("the DisplayPortInfo to default values.");
pw.println();
+ pw.println("Example enableUsbData");
+ pw.println("This dumpsys command functions for both simulated and real ports.");
+ pw.println(" dumpsys usb enable-usb-data \"matrix\" true");
+ pw.println(" dumpsys usb enable-usb-data \"matrix\" false");
+ pw.println();
pw.println("Example USB device descriptors:");
pw.println(" dumpsys usb dump-descriptors -dump-short");
pw.println(" dumpsys usb dump-descriptors -dump-tree");
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 63e91ad414de..7a0bf9038f7c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10523,6 +10523,8 @@ public class CarrierConfigManager {
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"LTE", new int[]{3731, 5965, 8618, 11179, 13384});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+ "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484});
+ auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index 241e69106718..f61cce666cca 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
@Deprecated public class MockAccountManager {
@@ -202,7 +200,7 @@ package android.test.mock {
method @Deprecated public int checkPermission(String, String);
method @Deprecated public int checkSignatures(String, String);
method @Deprecated public int checkSignatures(int, int);
- method public void clearInstantAppCookie();
+ method @Deprecated public void clearInstantAppCookie();
method @Deprecated public void clearPackagePreferredActivities(String);
method @Deprecated public String[] currentToCanonicalPackageNames(String[]);
method @Deprecated public void extendVerificationTimeout(int, int, long);
@@ -224,15 +222,15 @@ package android.test.mock {
method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.ChangedPackages getChangedPackages(int);
+ method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);
method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);
method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();
method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);
method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method @Deprecated public String getInstallerPackageName(String);
- method public byte[] getInstantAppCookie();
- method public int getInstantAppCookieMaxBytes();
+ method @Deprecated public byte[] getInstantAppCookie();
+ method @Deprecated public int getInstantAppCookieMaxBytes();
method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
@@ -241,7 +239,7 @@ package android.test.mock {
method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.PackageInstaller getPackageInstaller();
+ method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();
method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public String[] getPackagesForUid(int);
method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int);
@@ -265,8 +263,8 @@ package android.test.mock {
method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);
method @Deprecated public boolean hasSystemFeature(String);
method @Deprecated public boolean hasSystemFeature(String, int);
- method public boolean isInstantApp();
- method public boolean isInstantApp(String);
+ method @Deprecated public boolean isInstantApp();
+ method @Deprecated public boolean isInstantApp(String);
method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);
method @Deprecated public boolean isSafeMode();
method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -283,11 +281,12 @@ package android.test.mock {
method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);
method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int);
- method public void setApplicationCategoryHint(String, int);
+ method @Deprecated public void setApplicationCategoryHint(String, int);
method @Deprecated public void setApplicationEnabledSetting(String, int, int);
method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);
method @Deprecated public void setInstallerPackageName(String, String);
- method public void updateInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);
method @Deprecated public void verifyPendingInstall(int, int);
}
diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt
index fa2fbd276e9a..0c800b6f82b5 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -11,7 +9,7 @@ package android.test.mock {
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
method @Deprecated public String getDefaultBrowserPackageName(int);
method @Deprecated public boolean setDefaultBrowserPackageName(String, int);
- method public boolean setInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
}
}
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index 2b5132ecd14f..f35095743738 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -11,30 +9,30 @@ package android.test.mock {
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
- method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public boolean arePermissionsIndividuallyControlled();
+ method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method @Deprecated public boolean arePermissionsIndividuallyControlled();
method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String);
- method public String getDefaultBrowserPackageNameAsUser(int);
- method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
- method public android.graphics.drawable.Drawable getInstantAppIcon(String);
- method public android.content.ComponentName getInstantAppInstallerComponent();
- method public android.content.ComponentName getInstantAppResolverSettingsComponent();
- method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
- method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
- method public int getIntentVerificationStatusAsUser(String, int);
- method public int getPermissionFlags(String, String, android.os.UserHandle);
- method public void grantRuntimePermission(String, String, android.os.UserHandle);
- method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
- method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public void revokeRuntimePermission(String, String, android.os.UserHandle);
- method public boolean setDefaultBrowserPackageNameAsUser(String, int);
+ method @Deprecated public String getDefaultBrowserPackageNameAsUser(int);
+ method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+ method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String);
+ method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent();
+ method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent();
+ method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+ method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
+ method @Deprecated public int getIntentVerificationStatusAsUser(String, int);
+ method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle);
+ method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle);
+ method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
+ method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
+ method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
- method public void setUpdateAvailable(String, boolean);
- method public boolean updateIntentVerificationStatusAsUser(String, int, int);
- method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
- method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+ method @Deprecated public void setUpdateAvailable(String, boolean);
+ method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
+ method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
+ method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
}
}
diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/test-mock/api/system-removed.txt
+++ b/test-mock/api/system-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 1752edcd469e..9ed010881067 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -8,13 +6,13 @@ package android.test.mock {
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
- method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
- method public void clearCrossProfileIntentFilters(int);
- method public int getInstallReason(String, android.os.UserHandle);
- method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
- method public String[] getNamesForUids(int[]);
- method @NonNull public String getServicesSystemSharedLibraryPackageName();
- method @NonNull public String getSharedSystemSharedLibraryPackageName();
+ method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+ method @Deprecated public void clearCrossProfileIntentFilters(int);
+ method @Deprecated public int getInstallReason(String, android.os.UserHandle);
+ method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+ method @Deprecated public String[] getNamesForUids(int[]);
+ method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName();
+ method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();
}
}
diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt
index 14191ebcb080..d802177e249b 100644
--- a/test-mock/api/test-removed.txt
+++ b/test-mock/api/test-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 79348d104745..ae7c2a99b808 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -247,7 +247,7 @@ public class GraphicsActivity extends Activity {
int rc = 0;
try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
- transaction.setFrameRateCategory(mSurfaceControl, category);
+ transaction.setFrameRateCategory(mSurfaceControl, category, false);
transaction.apply();
}
return rc;
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
index 63acddf25e02..0f4798090da8 100644
--- a/tests/FlickerTests/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -61,9 +61,7 @@
<option name="shell-timeout" value="6600s"/>
<option name="test-timeout" value="6600s"/>
<option name="hidden-api-checks" value="false"/>
- <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed
<option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
- -->
<!-- PerfettoListener related arguments -->
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
<option name="instrumentation-arg"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 359845dc0de6..47d6d235848f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -36,7 +36,8 @@ import org.junit.runners.Parameterized
/**
* Test launching a secondary Activity into Picture-In-Picture mode.
*
- * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom
+ * 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`
@@ -63,7 +64,16 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
}
}
- /** Main and secondary activity start from a split each taking half of the screen. */
+ /**
+ * We expect the background layer to be visible during this transition.
+ */
+ @Presubmit
+ @Test
+ override fun backgroundLayerNeverVisible(): Unit {}
+
+ /**
+ * Main and secondary activity start from a split each taking half of the screen.
+ */
@Presubmit
@Test
fun layersStartFromEqualSplit() {
@@ -109,7 +119,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
.isVisible(TRANSITION_SNAPSHOT)
.isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true)
}
flicker.assertLayersEnd {
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index a617876a6da0..0599c1d01906 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -30,11 +30,6 @@ java_library_host {
],
libs: [
"junit",
- "ow2-asm",
- "ow2-asm-analysis",
- "ow2-asm-commons",
- "ow2-asm-tree",
- "ow2-asm-util",
],
static_libs: [
"guava",
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
index 4c37579ac917..7ada961d710f 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
@@ -17,8 +17,6 @@ package com.android.hoststubgen.hosthelper;
import static java.lang.annotation.ElementType.TYPE;
-import org.objectweb.asm.Type;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -29,6 +27,6 @@ import java.lang.annotation.Target;
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HostStubGenProcessedKeepClass {
- String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class);
+ String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class);
String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
index 34e0030f548a..e598da0a3cb9 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
@@ -17,8 +17,6 @@ package com.android.hoststubgen.hosthelper;
import static java.lang.annotation.ElementType.TYPE;
-import org.objectweb.asm.Type;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -29,6 +27,6 @@ import java.lang.annotation.Target;
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HostStubGenProcessedStubClass {
- String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class);
+ String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class);
String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index d176b5e97b0b..9f835979b238 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -15,8 +15,6 @@
*/
package com.android.hoststubgen.hosthelper;
-import org.objectweb.asm.Type;
-
import java.io.PrintStream;
import java.lang.StackWalker.Option;
import java.lang.reflect.Method;
@@ -32,7 +30,15 @@ public class HostTestUtils {
private HostTestUtils() {
}
- public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class);
+ /**
+ * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM
+ * in this JAR.
+ */
+ public static String getInternalName(final Class<?> clazz) {
+ return clazz.getName().replace('.', '/');
+ }
+
+ public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);
/** If true, we won't print method call log. */
private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index 722905f15b41..6bf074b1bf9f 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -16,14 +16,8 @@
source "${0%/*}"/../../common.sh
-#**********************************************************************************************
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#**********************************************************************************************
-
# This scripts run the "tiny-framework" test, but does most stuff from the command line, using
# the native java and javac commands.
-# This is useful to
-
debug=0
while getopts "d" opt; do
@@ -61,7 +55,7 @@ framework_compile_classpaths=(
test_compile_classpaths=(
$SOONG_INT/external/junit/junit/android_common/combined/junit.jar
- $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar
+ $ANDROID_HOST_OUT/framework/truth-prebuilt.jar
)
test_runtime_classpaths=(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 246d06526f5b..cd604ff97f73 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -23,6 +23,10 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
public class TinyFrameworkClassTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -140,4 +144,42 @@ public class TinyFrameworkClassTest {
assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
.doesNotContain(TinyFrameworkNestedClasses.class);
}
+
+ /**
+ * Test to try accessing JDK private fields using reflections + setAccessible(true),
+ * which is now disallowed due to Java Modules, unless you run the javacommand with.
+ * --add-opens=java.base/java.io=ALL-UNNAMED
+ *
+ * You can try it from the command line, like:
+ * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testFileDescriptor() throws Exception {
+ var fd = FileDescriptor.out;
+
+ // Get the FD value directly from the private field.
+ // This is now prohibited due to Java Modules.
+ // It throws:
+ // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa
+
+ thrown.expect(java.lang.reflect.InaccessibleObjectException.class);
+
+ // Access the private field.
+ final Field f = FileDescriptor.class.getDeclaredField("fd");
+ final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class);
+ f.setAccessible(true);
+ m.setAccessible(true);
+
+ assertThat(f.get(fd)).isEqualTo(1);
+
+ // Set
+ f.set(fd, 2);
+ assertThat(f.get(fd)).isEqualTo(2);
+
+ // Call the package private method, set(int).
+ m.invoke(fd, 0);
+ assertThat(f.get(fd)).isEqualTo(0);
+ }
}
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 2e9cf428354a..8bc88de608e5 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -34,8 +34,7 @@ run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
run ./hoststubgen/test-framework/run-test-without-atest.sh
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#run ./hoststubgen/test-tiny-framework/run-test-manually.sh
+run ./hoststubgen/test-tiny-framework/run-test-manually.sh
run atest tiny-framework-dump-test
run ./scripts/build-framework-hostside-jars-and-extract.sh
diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp
index ca96559ac016..40281d263a4c 100644
--- a/tools/lint/global/integration_tests/Android.bp
+++ b/tools/lint/global/integration_tests/Android.bp
@@ -12,25 +12,58 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_library {
- name: "AndroidGlobalLintTestNoAidl",
- srcs: ["TestNoAidl/**/*.java"],
+// Integration tests for @EnforcePermission linters.
+// Each test defines its own java_library. The XML lint report from this
+// java_library is wrapped under a Python library with a unique pkg_path (this
+// is to avoid a name conflict for the report file). All the tests are
+// referenced and executed by AndroidGlobalLintCheckerIntegrationTest.
+
+java_defaults {
+ name: "AndroidGlobalLintIntegrationTestDefault",
libs: [
"framework-annotations-lib",
],
lint: {
- // It is expected that lint returns an error when processing this
+ // It is expected that lint returns an error when processing the
// library. Silence it here, the lint output is verified in tests.py.
suppress_exit_code: true,
},
}
+java_library {
+ name: "AndroidGlobalLintTestNoAidl",
+ srcs: ["TestNoAidl/**/*.java"],
+ defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+ name: "AndroidGlobalLintTestNoAidl_py",
+ data: [":AndroidGlobalLintTestNoAidl{.lint}"],
+ pkg_path: "no_aidl",
+}
+
+java_library {
+ name: "AndroidGlobalLintTestMissingAnnotation",
+ srcs: [
+ "TestMissingAnnotation/**/*.java",
+ "TestMissingAnnotation/**/*.aidl",
+ ],
+ defaults: ["AndroidGlobalLintIntegrationTestDefault"],
+}
+
+python_library_host {
+ name: "AndroidGlobalLintTestMissingAnnotation_py",
+ data: [":AndroidGlobalLintTestMissingAnnotation{.lint}"],
+ pkg_path: "missing_annotation",
+}
+
python_test_host {
name: "AndroidGlobalLintCheckerIntegrationTest",
srcs: ["tests.py"],
main: "tests.py",
- data: [
- ":AndroidGlobalLintTestNoAidl{.lint}",
+ libs: [
+ "AndroidGlobalLintTestNoAidl_py",
+ "AndroidGlobalLintTestMissingAnnotation_py",
],
version: {
py3: {
diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
new file mode 100644
index 000000000000..9e4854c61f96
--- /dev/null
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.integration_tests;
+
+/**
+ * A class that implements an AIDL interface, but is missing the @EnforcePermission annotation.
+ */
+class TestMissingAnnotation extends IFoo.Stub {
+
+ @Override
+ public void Method() {
+ }
+
+}
diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
new file mode 100644
index 000000000000..95ec2c230599
--- /dev/null
+++ b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl
@@ -0,0 +1,7 @@
+package com.google.android.lint.integration_tests;
+
+interface IFoo {
+
+ @EnforcePermission("INTERNET")
+ void Method();
+}
diff --git a/tools/lint/global/integration_tests/tests.py b/tools/lint/global/integration_tests/tests.py
index fc3eeb4f8ed9..cdb16b8ba25f 100644
--- a/tools/lint/global/integration_tests/tests.py
+++ b/tools/lint/global/integration_tests/tests.py
@@ -19,16 +19,28 @@ import xml.etree.ElementTree
class TestLinterReports(unittest.TestCase):
"""Integration tests for the linters used by @EnforcePermission."""
- def test_no_aidl(self):
- report = pkgutil.get_data("lint", "lint-report.xml").decode()
+ def _read_report(self, pkg_path):
+ report = pkgutil.get_data(pkg_path, "lint/lint-report.xml").decode()
issues = xml.etree.ElementTree.fromstring(report)
self.assertEqual(issues.tag, "issues")
+ return issues
+
+ def test_no_aidl(self):
+ issues = self._read_report("no_aidl")
self.assertEqual(len(issues), 1)
issue = issues[0]
self.assertEqual(issue.attrib["id"], "MisusingEnforcePermissionAnnotation")
self.assertEqual(issue.attrib["severity"], "Error")
+ def test_missing_annotation(self):
+ issues = self._read_report("missing_annotation")
+ self.assertEqual(len(issues), 1)
+
+ issue = issues[0]
+ self.assertEqual(issue.attrib["id"], "MissingEnforcePermissionAnnotation")
+ self.assertEqual(issue.attrib["severity"], "Error")
+
if __name__ == '__main__':
unittest.main(verbosity=2)